From d4de9d4395b758b068a48ee948f23cb05160796d Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Sun, 11 Jan 2026 14:09:46 +0200 Subject: [PATCH 001/105] In Taboola adapter, added support for native and adjusted the existing banner support. Added and updated tests. --- modules/taboolaBidAdapter.js | 46 +++-- test/spec/modules/taboolaBidAdapter_spec.js | 197 +++++++++++++++++++- 2 files changed, 224 insertions(+), 19 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 421ddbd256b..e13085d4a26 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/boundingCli 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,7 +170,6 @@ export function getElementSignals(adUnitCode) { const converter = ortbConverter({ context: { netRevenue: true, - mediaType: BANNER, ttl: 300 }, imp(buildImp, bidRequest, context) { @@ -183,12 +183,26 @@ const converter = ortbConverter({ return reqData; }, bidResponse(buildBidResponse, bid, context) { + const hasNative = !!context.bidRequest?.mediaTypes?.native; + const hasBanner = !!context.bidRequest?.mediaTypes?.banner; + context.mediaType = hasNative && !hasBanner ? NATIVE : BANNER; + + // Unwrap native response - server returns {native: {...}} but ortbConverter expects {...} + 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,14 +211,19 @@ 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 = !!bidRequest.mediaTypes?.banner; + const hasNative = !!bidRequest.mediaTypes?.native; + return hasBanner || hasNative; }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; @@ -215,7 +234,9 @@ export const spec = { context: { auctionId } }); const {publisherId} = bidRequest.params; - const url = END_POINT_URL + '?publisher=' + publisherId; + const isNative = !!bidRequest.mediaTypes?.native; + const baseUrl = isNative ? NATIVE_ENDPOINT_URL : BANNER_ENDPOINT_URL; + const url = baseUrl + '?publisher=' + publisherId; return { url, @@ -433,7 +454,10 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data, context) { function fillTaboolaImpData(bid, imp) { const {tagId, position} = bid.params; - imp.banner = getBanners(bid, position); + const bannerSizes = bid.mediaTypes?.banner?.sizes; + if (bannerSizes) { + imp.banner = getBanners(bannerSizes, position); + } imp.tagid = tagId; if (typeof bid.getFloor === 'function') { @@ -476,9 +500,9 @@ function fillTaboolaImpData(bid, imp) { } } -function getBanners(bid, pos) { +function getBanners(sizes, pos) { return { - ...getSizes(bid.sizes), + ...getSizes(sizes), pos: pos } } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 8c23be4bcd0..1a31c94d7dd 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, 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 = () => ({ @@ -270,18 +274,18 @@ describe('Taboola Adapter', function () { const expectedData = { 'imp': [{ 'id': res.data.imp[0].id, - 'secure': 1, 'banner': { 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 +319,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$'); }); @@ -1980,4 +1984,181 @@ 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 () { + 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'); // TODO native isn't added! + 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 () { + 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'); + }); + }); + }); }) From d7fae2386bc7f9f1c3fae3a603299e671074a854 Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Sun, 11 Jan 2026 14:05:39 +0200 Subject: [PATCH 002/105] In Taboola adapter, added support for native and adjusted the existing banner support. Added and updated tests. --- .../gpt/taboola_native_test.html | 164 ++++++++++++++++++ modules/taboolaBidAdapter.js | 1 - 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/gpt/taboola_native_test.html diff --git a/integrationExamples/gpt/taboola_native_test.html b/integrationExamples/gpt/taboola_native_test.html new file mode 100644 index 00000000000..e8444d82ced --- /dev/null +++ b/integrationExamples/gpt/taboola_native_test.html @@ -0,0 +1,164 @@ + + + Taboola Native Prebid.js Example + + + + + + +

Taboola Native Prebid.js Example

+
Native Ad Container
+
+

Loading native ad...

+
+ + diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index e13085d4a26..d6da9f3fc50 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -187,7 +187,6 @@ const converter = ortbConverter({ const hasBanner = !!context.bidRequest?.mediaTypes?.banner; context.mediaType = hasNative && !hasBanner ? NATIVE : BANNER; - // Unwrap native response - server returns {native: {...}} but ortbConverter expects {...} if (context.mediaType === NATIVE) { const admObj = safeJSONParse(bid.adm); if (admObj?.native) { From 53fe2c5767939a8b7f7ba09a5335d581536aa470 Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Sun, 11 Jan 2026 14:37:25 +0200 Subject: [PATCH 003/105] removed test page pushed accidentally --- .../gpt/taboola_native_test.html | 164 ------------------ 1 file changed, 164 deletions(-) delete mode 100644 integrationExamples/gpt/taboola_native_test.html diff --git a/integrationExamples/gpt/taboola_native_test.html b/integrationExamples/gpt/taboola_native_test.html deleted file mode 100644 index e8444d82ced..00000000000 --- a/integrationExamples/gpt/taboola_native_test.html +++ /dev/null @@ -1,164 +0,0 @@ - - - Taboola Native Prebid.js Example - - - - - - -

Taboola Native Prebid.js Example

-
Native Ad Container
-
-

Loading native ad...

-
- - From a9754e9d53999f7241dd59b0bf4fa1c36f13bd50 Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Sun, 11 Jan 2026 16:27:30 +0200 Subject: [PATCH 004/105] Wrapped native tests with NATIVE feature check --- test/spec/modules/taboolaBidAdapter_spec.js | 162 ++++++++++---------- 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 1a31c94d7dd..e488ffacef2 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -2047,32 +2047,34 @@ describe('Taboola Adapter', function () { }); describe('buildRequests', function () { - 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(), - }; + 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); + 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'); // TODO native isn't added! - expect(res.data.imp[0].tagid).to.equal('native-placement'); - }); + 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 = { @@ -2099,66 +2101,68 @@ describe('Taboola Adapter', function () { }); describe('interpretResponse', function () { - 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(), - }; + 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 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' + 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' }], - seat: 'taboola' - }], - cur: 'USD' - } - }; + cur: 'USD' + } + }; - const res = spec.interpretResponse(serverResponse, request); + 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'); - }); + 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'); + }); + } }); }); }) From 3f7d7aeb4d1abf7c6a95f8b4f8b8cb58c37b0d13 Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Wed, 14 Jan 2026 15:57:32 +0200 Subject: [PATCH 005/105] updated media type checks --- modules/taboolaBidAdapter.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index d6da9f3fc50..d76b366c5e7 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -183,9 +183,8 @@ const converter = ortbConverter({ return reqData; }, bidResponse(buildBidResponse, bid, context) { - const hasNative = !!context.bidRequest?.mediaTypes?.native; - const hasBanner = !!context.bidRequest?.mediaTypes?.banner; - context.mediaType = hasNative && !hasBanner ? NATIVE : BANNER; + const { mediaType } = getMediaType(context.bidRequest); + context.mediaType = mediaType; if (context.mediaType === NATIVE) { const admObj = safeJSONParse(bid.adm); @@ -220,8 +219,7 @@ export const spec = { if (!hasPublisherAndTag) { return false; } - const hasBanner = !!bidRequest.mediaTypes?.banner; - const hasNative = !!bidRequest.mediaTypes?.native; + const { hasBanner, hasNative } = getMediaType(bidRequest); return hasBanner || hasNative; }, buildRequests: (validBidRequests, bidderRequest) => { @@ -233,8 +231,8 @@ export const spec = { context: { auctionId } }); const {publisherId} = bidRequest.params; - const isNative = !!bidRequest.mediaTypes?.native; - const baseUrl = isNative ? NATIVE_ENDPOINT_URL : BANNER_ENDPOINT_URL; + const { mediaType } = getMediaType(bidRequest); + const baseUrl = mediaType === NATIVE ? NATIVE_ENDPOINT_URL : BANNER_ENDPOINT_URL; const url = baseUrl + '?publisher=' + publisherId; return { @@ -453,15 +451,16 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data, context) { function fillTaboolaImpData(bid, imp) { const {tagId, position} = bid.params; - const bannerSizes = bid.mediaTypes?.banner?.sizes; - if (bannerSizes) { - imp.banner = getBanners(bannerSizes, position); + const { mediaType, hasBanner } = getMediaType(bid); + if (hasBanner) { + imp.banner = getBanners(bid.mediaTypes.banner.sizes, position); } - imp.tagid = tagId; + imp.tagid = tagId; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ currency: CURRENCY, + mediaType: mediaType, size: '*' }); if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { @@ -517,4 +516,14 @@ function getSizes(sizes) { } } +function getMediaType(bidRequest) { + const hasBanner = !!bidRequest?.mediaTypes?.banner?.sizes; + const hasNative = !!bidRequest?.mediaTypes?.native; + return { + hasBanner, + hasNative, + mediaType: hasNative && !hasBanner ? NATIVE : BANNER + }; +} + registerBidder(spec); From 15ad62f187fdb72d5d2494c104e28431f1d9279f Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Wed, 14 Jan 2026 16:08:24 +0200 Subject: [PATCH 006/105] added missing tab --- .github/workflows/jscpd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index c3021b2ced7..774493a7d04 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -36,7 +36,7 @@ jobs: ], "output": "./", "pattern": "**/*.js", - "ignore": ["**/*spec.js"] + "ignore": ["**/*spec.js"] }' > .jscpd.json - name: Run jscpd on entire codebase From 4cad25d142d6b7e4d51be82335b31a180ad24189 Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Mon, 2 Feb 2026 15:40:17 +0200 Subject: [PATCH 007/105] removed tab --- .github/workflows/jscpd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 774493a7d04..c3021b2ced7 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -36,7 +36,7 @@ jobs: ], "output": "./", "pattern": "**/*.js", - "ignore": ["**/*spec.js"] + "ignore": ["**/*spec.js"] }' > .jscpd.json - name: Run jscpd on entire codebase From 3081572cd108ff2fe5189205932a3fc89017b912 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:31:44 -0500 Subject: [PATCH 008/105] Bump actions/upload-artifact from 4 to 6 (#14402) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/PR-assignment-deps.yml | 4 ++-- .github/workflows/jscpd.yml | 2 +- .github/workflows/linter.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/PR-assignment-deps.yml b/.github/workflows/PR-assignment-deps.yml index 587555b705c..731cafa691c 100644 --- a/.github/workflows/PR-assignment-deps.yml +++ b/.github/workflows/PR-assignment-deps.yml @@ -20,7 +20,7 @@ jobs: run: | npx gulp build - name: Upload dependencies.json - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: dependencies.json path: ./build/dist/dependencies.json @@ -28,7 +28,7 @@ jobs: run: | echo '{ "prNo": ${{ github.event.pull_request.number }} }' >> ${{ runner.temp}}/prInfo.json - name: Upload PR info - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: prInfo path: ${{ runner.temp}}/prInfo.json diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index c3021b2ced7..91b89b018a9 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -119,7 +119,7 @@ jobs: - name: Upload comment data if: env.filtered_report_exists == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: comment path: ${{ runner.temp }}/comment.json diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 39fdcc4067b..52825abb00a 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -117,7 +117,7 @@ jobs: - name: Upload comment data if: ${{ steps.comment.outputs.result == 'true' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: comment path: ${{ runner.temp }}/comment.json From 9987e58bfbd3f01776f2fa11b474ed8511e75c57 Mon Sep 17 00:00:00 2001 From: Tomas Roos Date: Mon, 2 Feb 2026 21:41:10 +0100 Subject: [PATCH 009/105] Updated size id from rubicon production, api endpoint (#14377) Co-authored-by: Patrick McCann --- modules/rubiconBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index a3c05dabe5d..e8166c16f59 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -116,6 +116,7 @@ var sizeMap = { 210: '1080x1920', 213: '1030x590', 214: '980x360', + 219: '1920x1080', 221: '1x1', 229: '320x180', 230: '2000x1400', @@ -128,7 +129,6 @@ var sizeMap = { 259: '998x200', 261: '480x480', 264: '970x1000', - 265: '1920x1080', 274: '1800x200', 278: '320x500', 282: '320x400', From cd236ae6aee5dc168132e0e757fb07c90b97e690 Mon Sep 17 00:00:00 2001 From: SebRobert Date: Wed, 4 Feb 2026 09:56:07 +0100 Subject: [PATCH 010/105] BeOp Bid Adapter: Fix slot name detection to use best practices (#14399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix slot name detection to use best practices * Post review commit * Linter fix * Last post review commit List of concerned names managed | adUnitCode | Résultat | | ---------------------------------- | ---------------- | | `div-gpt-ad-article_top_123456` | `article_top` | | `div-gpt-ad-sidebar-1678459238475` | `sidebar` | | `div-gpt-ad-topbanner-1` | `topbanner-1` ✅ | | `div-gpt-ad-topbanner-2` | `topbanner-2` ✅ | | `sidebar-123456` | `sidebar-123456` | | `article_bottom` | `article_bottom` | * Only normalize GPT codes, leave others unchanged * Add tests on FIX and missing code coverage --- modules/beopBidAdapter.js | 40 ++++++++- test/spec/modules/beopBidAdapter_spec.js | 101 +++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 16a67a42432..13089b5be72 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -192,6 +192,40 @@ function buildTrackingParams(data, info, value) { }; } +function normalizeAdUnitCode(adUnitCode) { + if (!adUnitCode || typeof adUnitCode !== 'string') return undefined; + + // Only normalize GPT auto-generated adUnitCodes (div-gpt-ad-*) + // For non-GPT codes, return original string unchanged to preserve case + if (!/^div-gpt-ad[-_]/i.test(adUnitCode)) { + return adUnitCode; + } + + // GPT handling: strip prefix and random suffix + let slot = adUnitCode; + slot = slot.replace(/^div-gpt-ad[-_]?/i, ''); + + /** + * Remove only long numeric suffixes (likely auto-generated IDs). + * Preserve short numeric suffixes as they may be meaningful slot indices. + * + * Examples removed: + * div-gpt-ad-article_top_123456 → article_top + * div-gpt-ad-sidebar-1678459238475 → sidebar + * + * Examples preserved: + * div-gpt-ad-topbanner-1 → topbanner-1 + * div-gpt-ad-topbanner-2 → topbanner-2 + */ + slot = slot.replace(/([_-])\d{6,}$/, ''); + + slot = slot.toLowerCase().trim(); + + if (slot.length < 3) return undefined; + + return slot; +} + function beOpRequestSlotsMaker(bid, bidderRequest) { const bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); const publisherCurrency = getCurrencyFromBidderRequest(bidderRequest) || getValue(bid.params, 'currency') || 'EUR'; @@ -211,7 +245,11 @@ function beOpRequestSlotsMaker(bid, bidderRequest) { nptnid: getValue(bid.params, 'networkPartnerId'), bid: getBidIdParameter('bidId', bid), brid: getBidIdParameter('bidderRequestId', bid), - name: getBidIdParameter('adUnitCode', bid), + name: deepAccess(bid, 'ortb2Imp.ext.gpid') || + deepAccess(bid, 'ortb2Imp.ext.data.adslot') || + deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot') || + bid.ortb2Imp?.tagid || + normalizeAdUnitCode(bid.adUnitCode), tid: bid.ortb2Imp?.ext?.tid || '', brc: getBidIdParameter('bidRequestsCount', bid), bdrc: getBidIdParameter('bidderRequestCount', bid), diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 08e14b76182..3e4a439286d 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -389,6 +389,107 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.fg).to.exist; }) }) + describe('slot name normalization', function () { + it('should preserve non-GPT adUnitCode unchanged (case-sensitive)', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = '/Network/TopBanner'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('/Network/TopBanner'); + }); + + it('should preserve mixed-case custom adUnitCode unchanged', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'InArticleSlot'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('InArticleSlot'); + }); + + it('should normalize GPT auto-generated adUnitCode by removing prefix', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-article_top'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('article_top'); + }); + + it('should remove long numeric suffix from GPT adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-sidebar_123456'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('sidebar'); + }); + + it('should remove timestamp-like suffix from GPT adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-header-1678459238475'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('header'); + }); + + it('should preserve short numeric suffix in GPT adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-topbanner-1'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('topbanner-1'); + }); + + it('should preserve short numeric suffix like -2 in GPT adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-article_slot-2'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('article_slot-2'); + }); + + it('should handle GPT adUnitCode with underscore separator', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad_content_main'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('content_main'); + }); + + it('should return undefined for too short GPT slot names', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-ab'; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.be.undefined; + }); + + it('should prefer gpid over adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-fallback'; + bid.ortb2Imp = { ext: { gpid: '/123/preferred-slot' } }; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('/123/preferred-slot'); + }); + + it('should prefer adslot over adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-fallback'; + bid.ortb2Imp = { ext: { data: { adslot: '/456/adslot-name' } } }; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('/456/adslot-name'); + }); + + it('should prefer tagid over normalized adUnitCode', function () { + const bid = Object.assign({}, validBid); + bid.adUnitCode = 'div-gpt-ad-fallback'; + bid.ortb2Imp = { tagid: 'custom-tagid' }; + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + expect(payload.slts[0].name).to.equal('custom-tagid'); + }); + }); + describe('getUserSyncs', function () { it('should return iframe sync when iframeEnabled and syncFrame provided', function () { const syncOptions = { iframeEnabled: true, pixelEnabled: false }; From 54368476237f18e48ee1ea52828a1b105ad5d03b Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 4 Feb 2026 12:49:18 -0800 Subject: [PATCH 011/105] realTimeData: fix bug where setting circular references in FPD causes activity checks to get stuck in an infinite loop (#14366) * realTimeData: fix bug where setting circular references in FPD causes activity checks to get stuck in an infinite loop * handle circular references in unguarded properties * prefer allowing more data over avoiding leaks * more edge cases --- libraries/objectGuard/objectGuard.js | 27 ++++++ test/spec/activities/objectGuard_spec.js | 109 +++++++++++++++++------ 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/libraries/objectGuard/objectGuard.js b/libraries/objectGuard/objectGuard.js index 973e56aad5f..ff3f4b78c34 100644 --- a/libraries/objectGuard/objectGuard.js +++ b/libraries/objectGuard/objectGuard.js @@ -139,14 +139,40 @@ export function objectGuard(rules) { return true; } + const TARGET = Symbol('TARGET'); + function mkGuard(obj, tree, final, applies, cache = new WeakMap()) { // If this object is already proxied, return the cached proxy if (cache.has(obj)) { return cache.get(obj); } + /** + * Dereference (possibly nested) proxies to their underlying objects. + * + * This is to accommodate usage patterns like: + * + * guardedObject.property = [...guardedObject.property, additionalData]; + * + * where the `set` proxy trap would get an already proxied object as argument. + */ + function deref(obj, visited = new Set()) { + if (cache.has(obj?.[TARGET])) return obj[TARGET]; + if (obj == null || typeof obj !== 'object') return obj; + if (visited.has(obj)) return obj; + visited.add(obj); + Object.keys(obj).forEach(k => { + const sub = deref(obj[k], visited); + if (sub !== obj[k]) { + obj[k] = sub; + } + }) + return obj; + } + const proxy = new Proxy(obj, { get(target, prop, receiver) { + if (prop === TARGET) return target; const val = Reflect.get(target, prop, receiver); if (final && val != null && typeof val === 'object') { // a parent property has write protect rules, keep guarding @@ -175,6 +201,7 @@ export function objectGuard(rules) { return true; } } + newValue = deref(newValue); if (tree.children?.hasOwnProperty(prop)) { // apply all (possibly nested) write protect rules const curValue = Reflect.get(target, prop, receiver); diff --git a/test/spec/activities/objectGuard_spec.js b/test/spec/activities/objectGuard_spec.js index 3f1474b0c46..78f911aba2a 100644 --- a/test/spec/activities/objectGuard_spec.js +++ b/test/spec/activities/objectGuard_spec.js @@ -11,9 +11,11 @@ describe('objectGuard', () => { paths: ['foo', 'outer.inner.foo'], name: 'testRule', applies: sinon.stub().callsFake(() => applies), - get(val) { return `repl${val}` }, - } - }) + get(val) { + return `repl${val}`; + }, + }; + }); it('should reject conflicting rules', () => { const crule = {...rule, paths: ['outer']}; @@ -25,7 +27,7 @@ describe('objectGuard', () => { const guard = objectGuard([rule])({outer: {inner: {foo: 'bar'}}}); expect(guard.outer).to.equal(guard.outer); expect(guard.outer.inner).to.equal(guard.outer.inner); - }) + }); it('can prevent top level read access', () => { const obj = objectGuard([rule])({'foo': 1, 'other': 2}); expect(obj).to.eql({ @@ -44,7 +46,7 @@ describe('objectGuard', () => { const guarded = objectGuard([rule])(obj); obj.foo = 'baz'; expect(guarded.foo).to.eql('replbaz'); - }) + }); it('does not prevent access if applies returns false', () => { applies = false; @@ -52,7 +54,7 @@ describe('objectGuard', () => { expect(obj).to.eql({ foo: 1 }); - }) + }); it('can prevent nested property access', () => { const obj = objectGuard([rule])({ @@ -78,13 +80,13 @@ describe('objectGuard', () => { foo: 3 } } - }) + }); }); it('prevents nested property access when a parent property is protected', () => { const guard = objectGuard([rule])({foo: {inner: 'value'}}); expect(guard.inner?.value).to.not.exist; - }) + }); it('does not call applies more than once', () => { JSON.stringify(objectGuard([rule])({ @@ -96,7 +98,7 @@ describe('objectGuard', () => { } })); expect(rule.applies.callCount).to.equal(1); - }) + }); }); describe('write protection', () => { @@ -118,6 +120,55 @@ describe('objectGuard', () => { expect(obj.foo).to.eql({nested: 'item'}); }); + it('should handle circular references in guarded properties', () => { + applies = false; + const obj = { + foo: {} + }; + const guard = objectGuard([rule])(obj); + guard.foo.inner = guard.foo; + expect(guard).to.eql({ + foo: { + inner: guard.foo + } + }); + }); + + it('should handle circular references in unguarded properties', () => { + const obj = {}; + const guard = objectGuard([rule])(obj); + const val = {}; + val.circular = val; + guard.prop = val; + expect(guard).to.eql({ + prop: val + }) + }); + + it('should allow for deferred modification', () => { + const obj = {}; + const guard = objectGuard([rule])(obj); + const prop = {}; + guard.prop = prop; + prop.val = 'foo'; + expect(obj).to.eql({ + prop: { + val: 'foo' + } + }); + }); + + it('should not choke on immutable objects', () => { + const obj = {}; + const guard = objectGuard([rule])(obj); + guard.prop = Object.freeze({val: 'foo'}); + expect(obj).to.eql({ + prop: { + val: 'foo' + } + }) + }) + it('should reject conflicting rules', () => { const crule = {...rule, paths: ['outer']}; expect(() => objectGuard([rule, crule])).to.throw(); @@ -128,12 +179,12 @@ describe('objectGuard', () => { const guard = objectGuard([rule])({outer: {inner: {foo: 'bar'}}}); expect(guard.outer).to.equal(guard.outer); expect(guard.outer.inner).to.equal(guard.outer.inner); - }) + }); it('does not mess up array reads', () => { const guard = objectGuard([rule])({foo: [{bar: 'baz'}]}); expect(guard.foo).to.eql([{bar: 'baz'}]); - }) + }); it('prevents array modification', () => { const obj = {foo: ['value']}; @@ -141,7 +192,7 @@ describe('objectGuard', () => { guard.foo.pop(); guard.foo.push('test'); expect(obj.foo).to.eql(['value']); - }) + }); it('allows array modification when not applicable', () => { applies = false; @@ -150,7 +201,7 @@ describe('objectGuard', () => { guard.foo.pop(); guard.foo.push('test'); expect(obj.foo).to.eql(['test']); - }) + }); it('should prevent top-level writes', () => { const obj = {bar: {nested: 'val'}, other: 'val'}; @@ -166,7 +217,7 @@ describe('objectGuard', () => { const guard = objectGuard([rule])({foo: {some: 'value'}}); guard.foo = {some: 'value'}; sinon.assert.notCalled(rule.applies); - }) + }); it('should prevent top-level deletes', () => { const obj = {foo: {nested: 'val'}, bar: 'val'}; @@ -174,7 +225,7 @@ describe('objectGuard', () => { delete guard.foo.nested; delete guard.bar; expect(guard).to.eql({foo: {nested: 'val'}, bar: 'val'}); - }) + }); it('should prevent nested writes', () => { const obj = {outer: {inner: {bar: {nested: 'val'}, other: 'val'}}}; @@ -192,7 +243,7 @@ describe('objectGuard', () => { other: 'allowed' } } - }) + }); }); it('should prevent writes if upper levels are protected', () => { @@ -200,29 +251,29 @@ describe('objectGuard', () => { const guard = objectGuard([rule])(obj); guard.foo.inner.prop = 'value'; expect(obj).to.eql({foo: {inner: {}}}); - }) + }); it('should prevent deletes if a higher level property is protected', () => { const obj = {foo: {inner: {prop: 'value'}}}; const guard = objectGuard([rule])(obj); delete guard.foo.inner.prop; expect(obj).to.eql({foo: {inner: {prop: 'value'}}}); - }) + }); it('should clean up top-level writes that would result in inner properties changing', () => { const guard = objectGuard([rule])({outer: {inner: {bar: 'baz'}}}); guard.outer = {inner: {bar: 'baz', foo: 'baz', prop: 'allowed'}}; expect(guard).to.eql({outer: {inner: {bar: 'baz', prop: 'allowed'}}}); - }) + }); it('should not prevent writes that are not protected', () => { const obj = {}; const guard = objectGuard([rule])(obj); guard.outer = { test: 'value' - } + }; expect(obj.outer.test).to.eql('value'); - }) + }); it('should not choke on type mismatch: overwrite object with scalar', () => { const obj = {outer: {inner: {}}}; @@ -236,21 +287,21 @@ describe('objectGuard', () => { const guard = objectGuard([rule])(obj); guard.outer = {inner: {bar: 'denied', other: 'allowed'}}; expect(obj).to.eql({outer: {inner: {other: 'allowed'}}}); - }) + }); it('should prevent nested deletes', () => { const obj = {outer: {inner: {foo: {nested: 'val'}, bar: 'val'}}}; const guard = objectGuard([rule])(obj); delete guard.outer.inner.foo.nested; delete guard.outer.inner.bar; - expect(guard).to.eql({outer: {inner: {foo: {nested: 'val'}, bar: 'val'}}}) + expect(guard).to.eql({outer: {inner: {foo: {nested: 'val'}, bar: 'val'}}}); }); it('should prevent higher level deletes that would result in inner properties changing', () => { const guard = objectGuard([rule])({outer: {inner: {bar: 'baz'}}}); delete guard.outer.inner; expect(guard).to.eql({outer: {inner: {bar: 'baz'}}}); - }) + }); it('should work on null properties', () => { const obj = {foo: null}; @@ -293,7 +344,7 @@ describe('objectGuard', () => { applies: () => true, }) ]; - }) + }); Object.entries({ 'simple value': 'val', 'object value': {inner: 'val'} @@ -303,8 +354,8 @@ describe('objectGuard', () => { expect(obj.foo).to.not.exist; obj.foo = {other: 'val'}; expect(obj.foo).to.not.exist; - }) - }) - }) - }) + }); + }); + }); + }); }); From ae94b3b854afd5c1959a849db8106c619b52a892 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:49:52 -0500 Subject: [PATCH 012/105] Bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 (#14410) Bumps @isaacs/brace-expansion from 5.0.0 to 5.0.1. --- updated-dependencies: - dependency-name: "@isaacs/brace-expansion" dependency-version: 5.0.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7605928c52..007a8ba611d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2978,11 +2978,10 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dev": true, - "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" }, @@ -24091,9 +24090,9 @@ "dev": true }, "@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dev": true, "requires": { "@isaacs/balanced-match": "^4.0.1" From 969ac6aa2566aae1d5a002edd16c11c6eadf3e0b Mon Sep 17 00:00:00 2001 From: pgomulka-id5 Date: Thu, 5 Feb 2026 03:08:15 +0100 Subject: [PATCH 013/105] ID5 ID module: add option to use custom external targeting (#14324) * Add custom tag reporting mechanism for Id5IdSystem module * Add documentation * empty line rm, lint failure * empty line rm, lint failure * cannot use withResolvers in tests * type change in doc * fix example --- modules/id5IdSystem.js | 23 +- modules/id5IdSystem.md | 40 ++-- test/spec/modules/id5IdSystem_spec.js | 315 +++++++++++++++++++++++++- 3 files changed, 346 insertions(+), 32 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index d8f553c6ae0..c3e2e6c31ed 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -8,6 +8,7 @@ import { deepAccess, deepClone, + deepEqual, deepSetValue, isEmpty, isEmptyStr, @@ -118,6 +119,7 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleNam * @property {Array} [segments] - A list of segments to push to partners. Supported only in multiplexing. * @property {boolean} [disableUaHints] - When true, look up of high entropy values through user agent hints is disabled. * @property {string} [gamTargetingPrefix] - When set, the GAM targeting tags will be set and use the specified prefix, for example 'id5'. + * @property {boolean} [exposeTargeting] - When set, the ID5 targeting consumer mechanism will be enabled. */ /** @@ -569,17 +571,30 @@ function incrementNb(cachedObj) { } function updateTargeting(fetchResponse, config) { - if (config.params.gamTargetingPrefix) { - const tags = fetchResponse.tags; - if (tags) { + const tags = fetchResponse.tags; + if (tags) { + if (config.params.gamTargetingPrefix) { window.googletag = window.googletag || {cmd: []}; window.googletag.cmd = window.googletag.cmd || []; window.googletag.cmd.push(() => { for (const tag in tags) { - window.googletag.pubads().setTargeting(config.params.gamTargetingPrefix + '_' + tag, tags[tag]); + window.googletag.setConfig({targeting: {[config.params.gamTargetingPrefix + '_' + tag]: tags[tag]}}); } }); } + + if (config.params.exposeTargeting && !deepEqual(window.id5tags?.tags, tags)) { + window.id5tags = window.id5tags || {cmd: []}; + window.id5tags.cmd = window.id5tags.cmd || []; + window.id5tags.cmd.forEach(tagsCallback => { + setTimeout(() => tagsCallback(tags), 0); + }); + window.id5tags.cmd.push = function (tagsCallback) { + tagsCallback(tags) + Array.prototype.push.call(window.id5tags.cmd, tagsCallback); + }; + window.id5tags.tags = tags + } } } diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 363dd02e831..aaf34fa4054 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -33,7 +33,8 @@ pbjs.setConfig({ }, disableExtensions: false,// optional canCookieSync: true, // optional, has effect only when externalModuleUrl is used - gamTargetingPrefix: "id5" // optional, when set the ID5 module will set gam targeting paramaters with this prefix + gamTargetingPrefix: "id5", // optional, when set the ID5 module will set gam targeting paramaters with this prefix + exposeTargeting: false // optional, when set the ID5 module will execute `window.id5tags.cmd` callbacks for custom targeting tags }, storage: { type: 'html5', // "html5" is the required storage type @@ -47,25 +48,26 @@ pbjs.setConfig({ }); ``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | -| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | -| params | Required | Object | Details for the ID5 ID. | | -| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | -| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js -| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | -| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | -| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | -| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | -| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | -| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | -| params.canCookieSync | Optional | Boolean | Set this to `true` to enable cookie syncing with other ID5 partners. See [our documentation](https://wiki.id5.io/docs/initiate-cookie-sync-to-id5) for details. Default `false` | `true` or `false` | +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------| +| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | +| params | Required | Object | Details for the ID5 ID. | | +| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js +| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | +| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | +| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | +| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | +| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | +| params.canCookieSync | Optional | Boolean | Set this to `true` to enable cookie syncing with other ID5 partners. See [our documentation](https://wiki.id5.io/docs/initiate-cookie-sync-to-id5) for details. Default `false` | `true` or `false` | | params.gamTargetingPrefix | Optional | String | When this parameter is set the ID5 module will set appropriate GAM pubads targeting tags | `id5` | -| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | -| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | -| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | -| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | +| params.exposeTargeting | Optional | Boolean | When this parameter is set the ID5 module will execute `window.id5tags.cmd` callbacks for custom targeting tags | `false` | +| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 7249560c8c9..5964b683372 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1361,13 +1361,8 @@ describe('ID5 ID System', function () { setTargetingStub = sinon.stub(); window.googletag = { cmd: [], - pubads: function () { - return { - setTargeting: setTargetingStub - }; - } + setConfig: setTargetingStub }; - sinon.spy(window.googletag, 'pubads'); storedObject = utils.deepClone(ID5_STORED_OBJ); }); @@ -1391,14 +1386,16 @@ describe('ID5 ID System', function () { for (const [tagName, tagValue] of Object.entries(tagsObj)) { const fullTagName = `${targetingEnabledConfig.params.gamTargetingPrefix}_${tagName}`; - const matchingCall = setTargetingStub.getCalls().find(call => call.args[0] === fullTagName); + const matchingCall = setTargetingStub.getCalls().find(call => { + const config = call.args[0]; + return config.targeting && config.targeting[fullTagName] !== undefined; + }); expect(matchingCall, `Tag ${fullTagName} was not set`).to.exist; - expect(matchingCall.args[1]).to.equal(tagValue); + expect(matchingCall.args[0].targeting[fullTagName]).to.equal(tagValue); } window.googletag.cmd = []; setTargetingStub.reset(); - window.googletag.pubads.resetHistory(); } it('should not set GAM targeting if it is not enabled', function () { @@ -1435,6 +1432,306 @@ describe('ID5 ID System', function () { }) }) + describe('Decode should also expose targeting via id5tags if configured', function () { + let origId5tags, storedObject; + const exposeTargetingConfig = getId5FetchConfig(); + exposeTargetingConfig.params.gamTargetingPrefix = 'id5'; + exposeTargetingConfig.params.exposeTargeting = true; + + beforeEach(function () { + delete window.id5tags; + storedObject = utils.deepClone(ID5_STORED_OBJ); + }); + + afterEach(function () { + delete window.id5tags; + id5System.id5IdSubmodule._reset(); + }); + + it('should not expose targeting if exposeTargeting is not enabled', function () { + const config = getId5FetchConfig(); + config.params.gamTargetingPrefix = 'id5'; + // exposeTargeting is not set + const testObj = { + ...storedObject, + 'tags': { + 'id': 'y', + 'ab': 'n' + } + }; + id5System.id5IdSubmodule.decode(testObj, config); + expect(window.id5tags).to.be.undefined; + }); + + it('should not expose targeting if tags not returned from server', function () { + // tags is not in the response + id5System.id5IdSubmodule.decode(storedObject, exposeTargetingConfig); + expect(window.id5tags).to.be.undefined; + }); + + it('should create id5tags.cmd when it does not exist pre-decode', function () { + const testObj = { + ...storedObject, + 'tags': { + 'id': 'y', + 'ab': 'n' + } + }; + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + expect(window.id5tags).to.exist; + expect(window.id5tags.cmd).to.be.an('array'); + expect(window.id5tags.tags).to.deep.equal({ + 'id': 'y', + 'ab': 'n' + }); + }); + + it('should execute queued functions when cmd was created earlier', async function () { + const testTags = { + 'id': 'y', + 'ab': 'n', + 'enrich': 'y' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + const callTracker = []; + let resolvePromise; + const callbackPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + // Pre-create id5tags with queued functions + window.id5tags = { + cmd: [ + (tags) => callTracker.push({call: 1, tags: tags}), + (tags) => callTracker.push({call: 2, tags: tags}), + (tags) => { + callTracker.push({call: 3, tags: tags}); + resolvePromise(); + } + ] + }; + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + await callbackPromise; + + // Verify all queued functions were called with the tags + expect(callTracker).to.have.lengthOf(3); + expect(callTracker[0]).to.deep.equal({call: 1, tags: testTags}); + expect(callTracker[1]).to.deep.equal({call: 2, tags: testTags}); + expect(callTracker[2]).to.deep.equal({call: 3, tags: testTags}); + + // Verify tags were stored + expect(window.id5tags.tags).to.deep.equal(testTags); + }); + + it('should override push method to execute functions immediately', function () { + const testTags = { + 'id': 'y', + 'ab': 'n' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + // Now push a new function and verify it executes immediately + let callResult = null; + window.id5tags.cmd.push((tags) => { + callResult = {executed: true, tags: tags}; + }); + + expect(callResult).to.not.be.null; + expect(callResult.executed).to.be.true; + expect(callResult.tags).to.deep.equal(testTags); + }); + + it('should retrigger functions when tags are different but not when tags are the same', async function () { + const firstTags = { + 'id': 'y', + 'ab': 'n' + }; + const secondTags = { + 'id': 'y', + 'ab': 'y', + 'enrich': 'y' + }; + + const firstObj = { + ...storedObject, + 'tags': firstTags + }; + + const callTracker = []; + + // First decode + let resolveFirstPromise; + const firstCallbackPromise = new Promise((resolve) => { + resolveFirstPromise = resolve; + }); + + window.id5tags = { + cmd: [ + (tags) => { + callTracker.push({call: 'decode', tags: utils.deepClone(tags)}); + resolveFirstPromise(); + } + ] + }; + + id5System.id5IdSubmodule.decode(firstObj, exposeTargetingConfig); + + await firstCallbackPromise; + + expect(callTracker).to.have.lengthOf(1); + expect(callTracker[0].tags).to.deep.equal(firstTags); + + // Second decode with different tags - should retrigger + const secondObj = { + ...storedObject, + 'tags': secondTags + }; + + let resolveSecondPromise; + const secondCallbackPromise = new Promise((resolve) => { + resolveSecondPromise = resolve; + }); + + // Update the callback to resolve when called again + window.id5tags.cmd[0] = (tags) => { + callTracker.push({call: 'decode', tags: utils.deepClone(tags)}); + resolveSecondPromise(); + }; + + id5System.id5IdSubmodule.decode(secondObj, exposeTargetingConfig); + + await secondCallbackPromise; + + // The queued function should be called again with new tags + expect(callTracker).to.have.lengthOf(2); + expect(callTracker[1].tags).to.deep.equal(secondTags); + expect(window.id5tags.tags).to.deep.equal(secondTags); + + // Third decode with identical tags content (but different object reference) - should NOT retrigger + const thirdObj = { + ...storedObject, + 'tags': { + 'id': 'y', + 'ab': 'y', + 'enrich': 'y' + } + }; + + id5System.id5IdSubmodule.decode(thirdObj, exposeTargetingConfig); + + // Give it a small delay to ensure it doesn't retrigger + await new Promise(resolve => setTimeout(resolve, 50)); + + // With deepEqual, this should NOT retrigger since content is the same as secondTags + expect(callTracker).to.have.lengthOf(2); + expect(window.id5tags.tags).to.deep.equal(secondTags); + }); + + it('should handle when someone else has set id5tags.cmd earlier', async function () { + const testTags = { + 'id': 'y', + 'ab': 'n' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + const externalCallTracker = []; + let resolvePromise; + const callbackPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + // External script creates id5tags + window.id5tags = { + cmd: [], + externalData: 'some-external-value' + }; + + // Add external function + window.id5tags.cmd.push((tags) => { + externalCallTracker.push({external: true, tags: tags}); + resolvePromise(); + }); + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + await callbackPromise; + + // External function should be called + expect(externalCallTracker).to.have.lengthOf(1); + expect(externalCallTracker[0].external).to.be.true; + expect(externalCallTracker[0].tags).to.deep.equal(testTags); + + // External data should be preserved + expect(window.id5tags.externalData).to.equal('some-external-value'); + + // Tags should be set + expect(window.id5tags.tags).to.deep.equal(testTags); + }); + + it('should work with both gamTargetingPrefix and exposeTargeting enabled', async function () { + // Setup googletag + const origGoogletag = window.googletag; + window.googletag = { + cmd: [], + setConfig: sinon.stub() + }; + + const testTags = { + 'id': 'y', + 'ab': 'n' + }; + const testObj = { + ...storedObject, + 'tags': testTags + }; + + const callTracker = []; + let resolvePromise; + const callbackPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + window.id5tags = { + cmd: [(tags) => { + callTracker.push(tags); + resolvePromise(); + }] + }; + + id5System.id5IdSubmodule.decode(testObj, exposeTargetingConfig); + + await callbackPromise; + + // Both mechanisms should work + expect(window.googletag.cmd.length).to.be.at.least(1); + expect(callTracker).to.have.lengthOf(1); + expect(callTracker[0]).to.deep.equal(testTags); + expect(window.id5tags.tags).to.deep.equal(testTags); + + // Restore + if (origGoogletag) { + window.googletag = origGoogletag; + } else { + delete window.googletag; + } + }); + }); + describe('A/B Testing', function () { const expectedDecodedObjectWithIdAbOff = {id5id: {uid: ID5_STORED_ID, ext: {linkType: ID5_STORED_LINK_TYPE}}}; const expectedDecodedObjectWithIdAbOn = { From 155226fb48d1c5fcce4585c74805e6a59fdfd507 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 5 Feb 2026 03:09:29 +0100 Subject: [PATCH 014/105] Core: loading external scripts linting rule (#14354) * Core: loading external scripts linting rule * using default rule instead of custom * fixing overwritten event/adLoader rule * change to no-restricted-syntax * no-restricted-imports --------- Co-authored-by: Demetrio Girardi --- eslint.config.js | 28 ++++++++++- .../eslint/approvedLoadExternalScriptPaths.js | 47 +++++++++++++++++++ src/adloader.js | 44 +---------------- 3 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 plugins/eslint/approvedLoadExternalScriptPaths.js diff --git a/eslint.config.js b/eslint.config.js index a9b7fe04153..46d0eb86da4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,6 +9,7 @@ const path = require('path'); const _ = require('lodash'); const tseslint = require('typescript-eslint'); const {getSourceFolders} = require('./gulpHelpers.js'); +const APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS = require('./plugins/eslint/approvedLoadExternalScriptPaths.js'); function jsPattern(name) { return [`${name}/**/*.js`, `${name}/**/*.mjs`] @@ -122,6 +123,13 @@ module.exports = [ message: "Assigning a function to 'logResult, 'logMessage', 'logInfo', 'logWarn', or 'logError' is not allowed." }, ], + 'no-restricted-imports': [ + 'error', { + patterns: [ + '**/src/adloader.js' + ] + } + ], // Exceptions below this line are temporary (TM), so that eslint can be added into the CI process. // Violations of these styles should be fixed, and the exceptions removed over time. @@ -273,4 +281,22 @@ module.exports = [ '@typescript-eslint/no-require-imports': 'off' } }, -] + // Override: allow loadExternalScript import in approved files (excluding BidAdapters) + { + files: APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS.filter(p => !p.includes('BidAdapter')).map(p => { + // If path doesn't end with .js/.ts/.mjs, treat as folder pattern + if (!p.match(/\.(js|ts|mjs)$/)) { + return `${p}/**/*.{js,ts,mjs}`; + } + return p; + }), + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [] + } + ], + } + }, + ] diff --git a/plugins/eslint/approvedLoadExternalScriptPaths.js b/plugins/eslint/approvedLoadExternalScriptPaths.js new file mode 100644 index 00000000000..8a2d2d2a4d5 --- /dev/null +++ b/plugins/eslint/approvedLoadExternalScriptPaths.js @@ -0,0 +1,47 @@ +// List of exact file paths or folder paths where loadExternalScript is allowed to be used. +// Folder paths (without file extension) allow all files in that folder. +const APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS = [ + // Prebid maintained modules: + 'src/debugging.js', + 'src/Renderer.js', + // RTD modules: + 'modules/aaxBlockmeterRtdProvider.js', + 'modules/adagioRtdProvider.js', + 'modules/adlooxAnalyticsAdapter.js', + 'modules/arcspanRtdProvider.js', + 'modules/airgridRtdProvider.js', + 'modules/browsiRtdProvider.js', + 'modules/brandmetricsRtdProvider.js', + 'modules/cleanioRtdProvider.js', + 'modules/humansecurityMalvDefenseRtdProvider.js', + 'modules/humansecurityRtdProvider.js', + 'modules/confiantRtdProvider.js', + 'modules/contxtfulRtdProvider.js', + 'modules/hadronRtdProvider.js', + 'modules/mediafilterRtdProvider.js', + 'modules/medianetRtdProvider.js', + 'modules/azerionedgeRtdProvider.js', + 'modules/a1MediaRtdProvider.js', + 'modules/geoedgeRtdProvider.js', + 'modules/qortexRtdProvider.js', + 'modules/dynamicAdBoostRtdProvider.js', + 'modules/51DegreesRtdProvider.js', + 'modules/symitriDapRtdProvider.js', + 'modules/wurflRtdProvider.js', + 'modules/nodalsAiRtdProvider.js', + 'modules/anonymisedRtdProvider.js', + 'modules/optableRtdProvider.js', + 'modules/oftmediaRtdProvider.js', + // UserId Submodules + 'modules/justIdSystem.js', + 'modules/tncIdSystem.js', + 'modules/ftrackIdSystem.js', + 'modules/id5IdSystem.js', + // Test files + '**/*spec.js', + '**/*spec.ts', + '**/test/**/*', +]; + +module.exports = APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS; + diff --git a/src/adloader.js b/src/adloader.js index 098b78c211b..71f6698141e 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -5,45 +5,6 @@ import { isActivityAllowed } from './activities/rules.js'; import { insertElement, logError, logWarn, setScriptAttributes } from './utils.js'; const _requestCache = new WeakMap(); -// The below list contains modules or vendors whom Prebid allows to load external JS. -const _approvedLoadExternalJSList = [ - // Prebid maintained modules: - 'debugging', - 'outstream', - // RTD modules: - 'aaxBlockmeter', - 'adagio', - 'adloox', - 'arcspan', - 'airgrid', - 'browsi', - 'brandmetrics', - 'clean.io', - 'humansecurityMalvDefense', - 'humansecurity', - 'confiant', - 'contxtful', - 'hadron', - 'mediafilter', - 'medianet', - 'azerionedge', - 'a1Media', - 'geoedge', - 'qortex', - 'dynamicAdBoost', - '51Degrees', - 'symitridap', - 'wurfl', - 'nodalsAi', - 'anonymised', - 'optable', - 'oftmedia', - // UserId Submodules - 'justtag', - 'tncId', - 'ftrackId', - 'id5', -]; /** * Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy @@ -64,10 +25,7 @@ export function loadExternalScript(url, moduleType, moduleCode, callback, doc, a logError('cannot load external script without url and moduleCode'); return; } - if (!_approvedLoadExternalJSList.includes(moduleCode)) { - logError(`${moduleCode} not whitelisted for loading external JavaScript`); - return; - } + if (!doc) { doc = document; // provide a "valid" key for the WeakMap } From c3cea1def90b7d6074dc512be6a275ec736c9146 Mon Sep 17 00:00:00 2001 From: mpimentel-nexxen Date: Wed, 4 Feb 2026 18:11:48 -0800 Subject: [PATCH 015/105] Remove PAAPI-related functionality from Unruly adapter (#14358) * SBE-2291 Remove protected audience related test code * SBE-2291 Remove protected audience related code --- modules/unrulyBidAdapter.js | 38 +- test/spec/modules/unrulyBidAdapter_spec.js | 413 +-------------------- 2 files changed, 6 insertions(+), 445 deletions(-) diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 3f9c4dd1253..bf6b70e26db 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -56,12 +56,6 @@ const RemoveDuplicateSizes = (validBid) => { } }; -const ConfigureProtectedAudience = (validBid, protectedAudienceEnabled) => { - if (!protectedAudienceEnabled && validBid.ortb2Imp && validBid.ortb2Imp.ext) { - delete validBid.ortb2Imp.ext.ae; - } -} - const getRequests = (conf, validBidRequests, bidderRequest) => { const {bids, bidderRequestId, bidderCode, ...bidderRequestData} = bidderRequest; const invalidBidsCount = bidderRequest.bids.length - validBidRequests.length; @@ -71,7 +65,6 @@ const getRequests = (conf, validBidRequests, bidderRequest) => { const currSiteId = validBid.params.siteId; addBidFloorInfo(validBid); RemoveDuplicateSizes(validBid); - ConfigureProtectedAudience(validBid, conf.protectedAudienceEnabled); requestBySiteId[currSiteId] = requestBySiteId[currSiteId] || []; requestBySiteId[currSiteId].push(validBid); }); @@ -226,43 +219,18 @@ export const adapter = { 'options': { 'contentType': 'application/json' }, - 'protectedAudienceEnabled': bidderRequest.paapi?.enabled }, validBidRequests, bidderRequest); }, interpretResponse: function (serverResponse) { - if (!(serverResponse && serverResponse.body && (serverResponse.body.auctionConfigs || serverResponse.body.bids))) { + if (!(serverResponse && serverResponse.body && serverResponse.body.bids)) { return []; } const serverResponseBody = serverResponse.body; - let bids = []; - let fledgeAuctionConfigs = null; - if (serverResponseBody.bids.length) { - bids = handleBidResponseByMediaType(serverResponseBody.bids); - } - - if (serverResponseBody.auctionConfigs) { - const auctionConfigs = serverResponseBody.auctionConfigs; - const bidIdList = Object.keys(auctionConfigs); - if (bidIdList.length) { - bidIdList.forEach((bidId) => { - fledgeAuctionConfigs = [{ - 'bidId': bidId, - 'config': auctionConfigs[bidId] - }]; - }) - } - } + const bids = handleBidResponseByMediaType(serverResponseBody.bids); - if (!fledgeAuctionConfigs) { - return bids; - } - - return { - bids, - paapi: fledgeAuctionConfigs - }; + return bids; } }; diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index d73b9b6e8c7..38ec126bb73 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -42,20 +42,7 @@ describe('UnrulyAdapter', function () { } } - function createOutStreamExchangeAuctionConfig() { - return { - 'seller': 'https://nexxen.tech', - 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', - 'interestGroupBuyers': 'https://mydsp.com', - 'perBuyerSignals': { - 'https://mydsp.com': { - 'floor': 'bouttreefiddy' - } - } - } - }; - - function createExchangeResponse (bidList, auctionConfigs = null) { + function createExchangeResponse (bidList) { let bids = []; if (Array.isArray(bidList)) { bids = bidList; @@ -63,18 +50,9 @@ describe('UnrulyAdapter', function () { bids.push(bidList); } - if (!auctionConfigs) { - return { - 'body': {bids} - }; - } - return { - 'body': { - bids, - auctionConfigs - } - } + 'body': {bids} + }; }; const inStreamServerResponse = { @@ -692,231 +670,6 @@ describe('UnrulyAdapter', function () { const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); expect(result[0].data).to.deep.equal(expectedResult); }); - describe('Protected Audience Support', function() { - it('should return an array with 2 items and enabled protected audience', function () { - mockBidRequests = { - 'bidderCode': 'unruly', - 'paapi': { - enabled: true - }, - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - }, - { - 'bidder': 'unruly', - 'params': { - 'siteId': 2234554, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - } - ] - }; - - const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); - expect(typeof result).to.equal('object'); - expect(result.length).to.equal(2); - expect(result[0].data.bidderRequest.bids.length).to.equal(1); - expect(result[1].data.bidderRequest.bids.length).to.equal(1); - expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); - expect(result[1].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); - }); - it('should return an array with 2 items and enabled protected audience on only one unit', function () { - mockBidRequests = { - 'bidderCode': 'unruly', - 'paapi': { - enabled: true - }, - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - }, - { - 'bidder': 'unruly', - 'params': { - 'siteId': 2234554, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': {} - } - } - ] - }; - - const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); - expect(typeof result).to.equal('object'); - expect(result.length).to.equal(2); - expect(result[0].data.bidderRequest.bids.length).to.equal(1); - expect(result[1].data.bidderRequest.bids.length).to.equal(1); - expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); - expect(result[1].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.be.undefined; - }); - it('disables configured protected audience when fledge is not availble', function () { - mockBidRequests = { - 'bidderCode': 'unruly', - 'fledgeEnabled': false, - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': [ - 'video/mp4' - ], - 'playerSize': [ - [ - 640, - 480 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'sizes': [ - [ - 640, - 480 - ] - ], - 'bidId': '27a3ee1626a5c7', - 'bidderRequestId': '12e00d17dff07b', - 'ortb2Imp': { - 'ext': { - 'ae': 1 - } - } - } - ] - }; - - const result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); - expect(typeof result).to.equal('object'); - expect(result.length).to.equal(1); - expect(result[0].data.bidderRequest.bids.length).to.equal(1); - expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.be.undefined; - }); - }); }); describe('interpretResponse', function () { @@ -967,166 +720,6 @@ describe('UnrulyAdapter', function () { ]); }); - it('should return object with an array of bids and an array of auction configs when it receives a successful response from server', function () { - const bidId = '27a3ee1626a5c7' - const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); - const mockExchangeAuctionConfig = {}; - mockExchangeAuctionConfig[bidId] = createOutStreamExchangeAuctionConfig(); - const mockServerResponse = createExchangeResponse(mockExchangeBid, mockExchangeAuctionConfig); - const originalRequest = { - 'data': { - 'bidderRequest': { - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 640, - 480 - ], - [ - 640, - 480 - ], - [ - 300, - 250 - ], - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'bidId': bidId, - 'bidderRequestId': '12e00d17dff07b', - } - ] - } - } - }; - - expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ - 'bids': [ - { - 'ext': { - 'statusCode': 1, - 'renderer': { - 'id': 'unruly_inarticle', - 'config': { - 'siteId': 123456, - 'targetingUUID': 'xxx-yyy-zzz' - }, - 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' - }, - 'adUnitCode': 'video1' - }, - requestId: 'mockBidId', - bidderCode: 'unruly', - cpm: 20, - width: 323, - height: 323, - vastUrl: 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22https%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', - netRevenue: true, - creativeId: 'mockBidId', - ttl: 360, - 'meta': { - 'mediaType': 'video', - 'videoContext': 'outstream' - }, - currency: 'USD', - renderer: fakeRenderer, - mediaType: 'video' - } - ], - 'paapi': [{ - 'bidId': bidId, - 'config': { - 'seller': 'https://nexxen.tech', - 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', - 'interestGroupBuyers': 'https://mydsp.com', - 'perBuyerSignals': { - 'https://mydsp.com': { - 'floor': 'bouttreefiddy' - } - } - } - }] - }); - }); - - it('should return object with an array of auction configs when it receives a successful response from server without bids', function () { - const bidId = '27a3ee1626a5c7'; - const mockExchangeAuctionConfig = {}; - mockExchangeAuctionConfig[bidId] = createOutStreamExchangeAuctionConfig(); - const mockServerResponse = createExchangeResponse(null, mockExchangeAuctionConfig); - const originalRequest = { - 'data': { - 'bidderRequest': { - 'bids': [ - { - 'bidder': 'unruly', - 'params': { - 'siteId': 233261, - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 640, - 480 - ], - [ - 640, - 480 - ], - [ - 300, - 250 - ], - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'video2', - 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', - 'bidId': bidId, - 'bidderRequestId': '12e00d17dff07b' - } - ] - } - } - }; - - expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ - 'bids': [], - 'paapi': [{ - 'bidId': bidId, - 'config': { - 'seller': 'https://nexxen.tech', - 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', - 'interestGroupBuyers': 'https://mydsp.com', - 'perBuyerSignals': { - 'https://mydsp.com': { - 'floor': 'bouttreefiddy' - } - } - } - }] - }); - }); - it('should initialize and set the renderer', function () { expect(Renderer.install.called).to.be.false; expect(fakeRenderer.setRender.called).to.be.false; From 8c2ad77d0ab76b74b9c8bbed6f253e080f98fe8e Mon Sep 17 00:00:00 2001 From: Vadym Shatov <135347097+Gunnar97@users.noreply.github.com> Date: Thu, 5 Feb 2026 04:12:52 +0200 Subject: [PATCH 016/105] New library: placement position &bbidmaticBidAdapter: update viewability tracking logic (#14372) * bidmaticBidAdapter: update viewability tracking logic * bidmaticBidAdapter: update viewability tracking logic * bidmaticBidAdapter: update viewability tracking logic * bidmaticBidAdapter: update viewability tracking logic --- .../placementPositionInfo.js | 141 ++++ modules/bidmaticBidAdapter.js | 53 +- .../libraries/placementPositionInfo_spec.js | 741 ++++++++++++++++++ 3 files changed, 887 insertions(+), 48 deletions(-) create mode 100644 libraries/placementPositionInfo/placementPositionInfo.js create mode 100644 test/spec/libraries/placementPositionInfo_spec.js diff --git a/libraries/placementPositionInfo/placementPositionInfo.js b/libraries/placementPositionInfo/placementPositionInfo.js new file mode 100644 index 00000000000..b5bdf47fb2d --- /dev/null +++ b/libraries/placementPositionInfo/placementPositionInfo.js @@ -0,0 +1,141 @@ +import {getBoundingClientRect} from '../boundingClientRect/boundingClientRect.js'; +import {canAccessWindowTop, cleanObj, getWindowSelf, getWindowTop} from '../../src/utils.js'; +import {getViewability} from '../percentInView/percentInView.js'; + +export function getPlacementPositionUtils() { + const topWin = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); + const selfWin = getWindowSelf(); + + const findElementWithContext = (adUnitCode) => { + let element = selfWin.document.getElementById(adUnitCode); + if (element) { + return {element, frameOffset: getFrameOffsetForCurrentWindow()}; + } + + const searchInIframes = (doc, accumulatedOffset = {top: 0}, iframeWindow = null) => { + try { + const element = doc.getElementById(adUnitCode); + if (element) { + return {element, frameOffset: accumulatedOffset, iframeWindow}; + } + + const frames = doc.getElementsByTagName('iframe'); + for (const frame of frames) { + try { + const iframeDoc = frame.contentDocument || frame.contentWindow?.document; + if (iframeDoc) { + const frameRect = getBoundingClientRect(frame); + const newOffset = { + top: accumulatedOffset.top + frameRect.top + }; + const result = searchInIframes(iframeDoc, newOffset, frame.contentWindow); + if (result) { + return result; + } + } + } catch (_e) { + } + } + } catch (_e) { + } + return null; + }; + + const result = searchInIframes(selfWin.document); + return result || {element: null, frameOffset: {top: 0}}; + }; + + const getFrameOffsetForCurrentWindow = () => { + if (topWin === selfWin) { + return {top: 0}; + } + try { + const frames = topWin.document.getElementsByTagName('iframe'); + for (const frame of frames) { + if (frame.contentWindow === selfWin) { + return {top: getBoundingClientRect(frame).top}; + } + } + } catch (_e) { + return {top: 0}; + } + return {top: 0}; + }; + + const getViewportHeight = () => { + return topWin.innerHeight || topWin.document.documentElement.clientHeight || topWin.document.body.clientHeight || 0; + }; + + const getPageHeight = () => { + const body = topWin.document.body; + const html = topWin.document.documentElement; + if (!body || !html) return 0; + + return Math.max( + body.scrollHeight, + body.offsetHeight, + html.clientHeight, + html.scrollHeight, + html.offsetHeight + ); + }; + + const getViewableDistance = (element, frameOffset) => { + if (!element) return {distanceToView: 0, elementHeight: 0}; + + const elementRect = getBoundingClientRect(element); + if (!elementRect) return {distanceToView: 0, elementHeight: 0}; + + const elementTop = elementRect.top + frameOffset.top; + const elementBottom = elementRect.bottom + frameOffset.top; + const viewportHeight = getViewportHeight(); + + let distanceToView; + if (elementTop - viewportHeight <= 0 && elementBottom >= 0) { + distanceToView = 0; + } else if (elementTop - viewportHeight > 0) { + distanceToView = Math.round(elementTop - viewportHeight); + } else { + distanceToView = Math.round(elementBottom); + } + + return {distanceToView, elementHeight: elementRect.height}; + }; + + function getPlacementInfo(bidReq) { + const {element, frameOffset, iframeWindow} = findElementWithContext(bidReq.adUnitCode); + const {distanceToView, elementHeight} = getViewableDistance(element, frameOffset); + + const sizes = (bidReq.sizes || []).map(size => ({ + w: Number.parseInt(size[0], 10), + h: Number.parseInt(size[1], 10) + })); + const size = sizes.length > 0 + ? sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min, sizes[0]) + : {}; + + const winForViewability = iframeWindow || topWin; + const placementPercentView = element ? getViewability(element, winForViewability, size) : 0; + + return cleanObj({ + AuctionsCount: bidReq.auctionsCount, + DistanceToView: distanceToView, + PlacementPercentView: Math.round(placementPercentView), + ElementHeight: Math.round(elementHeight) || 1 + }); + } + + function getPlacementEnv() { + return cleanObj({ + TimeFromNavigation: Math.floor(performance.now()), + TabActive: topWin.document.visibilityState === 'visible', + PageHeight: getPageHeight(), + ViewportHeight: getViewportHeight() + }); + } + + return { + getPlacementInfo, + getPlacementEnv + }; +} diff --git a/modules/bidmaticBidAdapter.js b/modules/bidmaticBidAdapter.js index 4265b48428f..daa379421b6 100644 --- a/modules/bidmaticBidAdapter.js +++ b/modules/bidmaticBidAdapter.js @@ -4,7 +4,6 @@ import { cleanObj, deepAccess, flatten, - getWinDimensions, isArray, isNumber, logWarn, @@ -13,7 +12,7 @@ import { import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { chunk } from '../libraries/chunk/chunk.js'; -import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import {getPlacementPositionUtils} from "../libraries/placementPositionInfo/placementPositionInfo.js"; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -21,10 +20,13 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec */ +const ADAPTER_VERSION = 'v1.0.0'; const URL = 'https://adapter.bidmatic.io/bdm/auction'; const BIDDER_CODE = 'bidmatic'; const SYNCS_DONE = new Set(); +const { getPlacementEnv, getPlacementInfo } = getPlacementPositionUtils() + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, @@ -144,6 +146,7 @@ export function parseResponseBody(serverResponse, adapterRequest) { export function remapBidRequest(bidRequests, adapterRequest) { const bidRequestBody = { + AdapterVersion: ADAPTER_VERSION, Domain: deepAccess(adapterRequest, 'refererInfo.page'), ...getPlacementEnv() }; @@ -237,50 +240,4 @@ export function createBid(bidResponse) { }; } -function getPlacementInfo(bidReq) { - const placementElementNode = document.getElementById(bidReq.adUnitCode); - try { - return cleanObj({ - AuctionsCount: bidReq.auctionsCount, - DistanceToView: getViewableDistance(placementElementNode) - }); - } catch (e) { - logWarn('Error while getting placement info', e); - return {}; - } -} - -/** - * @param element - */ -function getViewableDistance(element) { - if (!element) return 0; - const elementRect = getBoundingClientRect(element); - - if (!elementRect) { - return 0; - } - - const elementMiddle = elementRect.top + (elementRect.height / 2); - const viewportHeight = getWinDimensions().innerHeight - if (elementMiddle > window.scrollY + viewportHeight) { - // element is below the viewport - return Math.round(elementMiddle - (window.scrollY + viewportHeight)); - } - // element is above the viewport -> negative value - return Math.round(elementMiddle); -} - -function getPageHeight() { - return document.documentElement.scrollHeight || document.body.scrollHeight; -} - -function getPlacementEnv() { - return cleanObj({ - TimeFromNavigation: Math.floor(performance.now()), - TabActive: document.visibilityState === 'visible', - PageHeight: getPageHeight() - }) -} - registerBidder(spec); diff --git a/test/spec/libraries/placementPositionInfo_spec.js b/test/spec/libraries/placementPositionInfo_spec.js new file mode 100644 index 00000000000..11a3aa1aa0f --- /dev/null +++ b/test/spec/libraries/placementPositionInfo_spec.js @@ -0,0 +1,741 @@ +import { getPlacementPositionUtils } from '../../../libraries/placementPositionInfo/placementPositionInfo.js'; +import * as utils from '../../../src/utils.js'; +import * as boundingClientRectLib from '../../../libraries/boundingClientRect/boundingClientRect.js'; +import * as percentInViewLib from '../../../libraries/percentInView/percentInView.js'; +import assert from 'assert'; +import sinon from 'sinon'; + +describe('placementPositionInfo', function () { + let sandbox; + let canAccessWindowTopStub; + let getWindowTopStub; + let getWindowSelfStub; + let getBoundingClientRectStub; + let percentInViewStub; + let cleanObjStub; + + let mockDocument; + let mockWindow; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + + mockDocument = { + getElementById: sandbox.stub().returns(null), + getElementsByTagName: sandbox.stub().returns([]), + body: { scrollHeight: 2000, offsetHeight: 1800 }, + documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 }, + visibilityState: 'visible' + }; + + mockWindow = { + innerHeight: 800, + document: mockDocument + }; + + canAccessWindowTopStub = sandbox.stub(utils, 'canAccessWindowTop').returns(true); + getWindowTopStub = sandbox.stub(utils, 'getWindowTop').returns(mockWindow); + getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf').returns(mockWindow); + getBoundingClientRectStub = sandbox.stub(boundingClientRectLib, 'getBoundingClientRect'); + percentInViewStub = sandbox.stub(percentInViewLib, 'getViewability'); + cleanObjStub = sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('getPlacementPositionUtils', function () { + it('should return an object with getPlacementInfo and getPlacementEnv functions', function () { + const result = getPlacementPositionUtils(); + + assert.strictEqual(typeof result.getPlacementInfo, 'function'); + assert.strictEqual(typeof result.getPlacementEnv, 'function'); + }); + + it('should use window top when accessible', function () { + canAccessWindowTopStub.returns(true); + getPlacementPositionUtils(); + + assert.ok(getWindowTopStub.called); + }); + + it('should use window self when top is not accessible', function () { + canAccessWindowTopStub.returns(false); + getPlacementPositionUtils(); + + assert.ok(getWindowSelfStub.called); + }); + }); + + describe('getPlacementInfo', function () { + let getPlacementInfo; + let mockElement; + + beforeEach(function () { + mockElement = { id: 'test-ad-unit' }; + mockDocument.getElementById.returns(mockElement); + + getBoundingClientRectStub.returns({ + top: 100, + bottom: 200, + height: 100, + width: 300 + }); + + percentInViewStub.returns(50); + + const placementUtils = getPlacementPositionUtils(); + getPlacementInfo = placementUtils.getPlacementInfo; + }); + + it('should return placement info with all required fields', function () { + const bidReq = { + adUnitCode: 'test-ad-unit', + auctionsCount: 5, + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.AuctionsCount, 5); + assert.strictEqual(typeof result.DistanceToView, 'number'); + assert.strictEqual(typeof result.PlacementPercentView, 'number'); + assert.strictEqual(typeof result.ElementHeight, 'number'); + }); + + it('should calculate distanceToView as 0 when element is in viewport', function () { + getBoundingClientRectStub.returns({ + top: 100, + bottom: 200, + height: 100 + }); + + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should calculate positive distanceToView when element is below viewport', function () { + getBoundingClientRectStub.returns({ + top: 1000, + bottom: 1100, + height: 100 + }); + + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, 200); + }); + + it('should calculate negative distanceToView when element is above viewport', function () { + getBoundingClientRectStub.returns({ + top: -200, + bottom: -100, + height: 100 + }); + + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, -100); + }); + + it('should handle null element gracefully', function () { + mockDocument.getElementById.returns(null); + + const bidReq = { + adUnitCode: 'non-existent-unit', + sizes: [[300, 250]] + }; + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, 0); + assert.strictEqual(result.ElementHeight, 1); + }); + + it('should not call getViewability when element is null', function () { + mockDocument.getElementById.returns(null); + + const bidReq = { + adUnitCode: 'non-existent-unit', + sizes: [[300, 250]] + }; + + const placementUtils = getPlacementPositionUtils(); + placementUtils.getPlacementInfo(bidReq); + + assert.ok(!percentInViewStub.called, 'getViewability should not be called with null element'); + }); + + it('should handle empty sizes array', function () { + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [] + }; + + const result = getPlacementInfo(bidReq); + + assert.ok(!isNaN(result.PlacementPercentView), 'PlacementPercentView should not be NaN'); + }); + + it('should handle undefined sizes', function () { + const bidReq = { + adUnitCode: 'test-ad-unit' + }; + + const result = getPlacementInfo(bidReq); + + assert.ok(!isNaN(result.PlacementPercentView), 'PlacementPercentView should not be NaN'); + }); + + it('should select the smallest size by area', function () { + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [[728, 90], [300, 250], [160, 600]] + }; + + getPlacementInfo(bidReq); + + const percentInViewCall = percentInViewStub.getCall(0); + const sizeArg = percentInViewCall.args[2]; + + assert.strictEqual(sizeArg.w, 728); + assert.strictEqual(sizeArg.h, 90); + }); + + it('should use ElementHeight from bounding rect', function () { + getBoundingClientRectStub.returns({ + top: 100, + bottom: 350, + height: 250 + }); + + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.ElementHeight, 250); + }); + + it('should default ElementHeight to 1 when height is 0', function () { + getBoundingClientRectStub.returns({ + top: 100, + bottom: 100, + height: 0 + }); + + const bidReq = { + adUnitCode: 'test-ad-unit', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.ElementHeight, 1); + }); + }); + + describe('getPlacementEnv', function () { + let getPlacementEnv; + let performanceNowStub; + + beforeEach(function () { + performanceNowStub = sandbox.stub(performance, 'now').returns(1234.567); + + const placementUtils = getPlacementPositionUtils(); + getPlacementEnv = placementUtils.getPlacementEnv; + }); + + it('should return environment info with all required fields', function () { + const result = getPlacementEnv(); + + assert.strictEqual(typeof result.TimeFromNavigation, 'number'); + assert.strictEqual(typeof result.TabActive, 'boolean'); + assert.strictEqual(typeof result.PageHeight, 'number'); + assert.strictEqual(typeof result.ViewportHeight, 'number'); + }); + + it('should return TimeFromNavigation as floored performance.now()', function () { + performanceNowStub.returns(5678.999); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementEnv(); + + assert.strictEqual(result.TimeFromNavigation, 5678); + }); + + it('should return TabActive as true when document is visible', function () { + const result = getPlacementEnv(); + + assert.strictEqual(result.TabActive, true); + }); + + it('should return TabActive as false when document is hidden', function () { + sandbox.restore(); + sandbox = sinon.createSandbox(); + + const hiddenMockDocument = { + getElementById: sandbox.stub().returns(null), + getElementsByTagName: sandbox.stub().returns([]), + body: { scrollHeight: 1000, offsetHeight: 1000 }, + documentElement: { clientHeight: 1000, scrollHeight: 1000, offsetHeight: 1000 }, + visibilityState: 'hidden' + }; + + const hiddenMockWindow = { + innerHeight: 800, + document: hiddenMockDocument + }; + + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getWindowTop').returns(hiddenMockWindow); + sandbox.stub(utils, 'getWindowSelf').returns(hiddenMockWindow); + sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); + sandbox.stub(performance, 'now').returns(1000); + + const freshUtils = getPlacementPositionUtils(); + const result = freshUtils.getPlacementEnv(); + + assert.strictEqual(result.TabActive, false); + }); + + it('should return ViewportHeight from window.innerHeight', function () { + const result = getPlacementEnv(); + + assert.strictEqual(result.ViewportHeight, 800); + }); + + it('should return max PageHeight from all document height properties', function () { + const result = getPlacementEnv(); + + assert.strictEqual(result.PageHeight, 2100); + }); + }); + + describe('getViewableDistance edge cases', function () { + let getPlacementInfo; + + beforeEach(function () { + mockDocument.getElementById.returns({ id: 'test' }); + percentInViewStub.returns(0); + + const placementUtils = getPlacementPositionUtils(); + getPlacementInfo = placementUtils.getPlacementInfo; + }); + + it('should handle getBoundingClientRect returning null', function () { + getBoundingClientRectStub.returns(null); + + const bidReq = { + adUnitCode: 'test', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, 0); + assert.strictEqual(result.ElementHeight, 1); + }); + + it('should handle element exactly at viewport bottom edge', function () { + getBoundingClientRectStub.returns({ + top: 800, + bottom: 900, + height: 100 + }); + + const bidReq = { + adUnitCode: 'test', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should handle element exactly at viewport top edge', function () { + getBoundingClientRectStub.returns({ + top: 0, + bottom: 100, + height: 100 + }); + + const bidReq = { + adUnitCode: 'test', + sizes: [[300, 250]] + }; + + const result = getPlacementInfo(bidReq); + + assert.strictEqual(result.DistanceToView, 0); + }); + }); + + describe('iframe coordinate translation', function () { + let mockSelfDoc; + let mockSelfWindow; + let mockTopWindow; + let mockIframe; + let iframeTop; + + beforeEach(function () { + iframeTop = 0; + mockSelfDoc = { + getElementById: sandbox.stub().returns({ id: 'test' }), + getElementsByTagName: sandbox.stub().returns([]) + }; + mockSelfWindow = { + innerHeight: 500, + document: mockSelfDoc + }; + mockTopWindow = { + innerHeight: 1000, + document: { + getElementsByTagName: sinon.stub(), + body: { scrollHeight: 2000, offsetHeight: 1800 }, + documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } + } + }; + mockIframe = { + contentWindow: mockSelfWindow + }; + + sandbox.restore(); + sandbox = sinon.createSandbox(); + + mockSelfDoc.getElementById = sandbox.stub().returns({ id: 'test' }); + mockSelfDoc.getElementsByTagName = sandbox.stub().returns([]); + mockTopWindow.document.getElementsByTagName = sandbox.stub(); + + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getWindowTop').returns(mockTopWindow); + sandbox.stub(utils, 'getWindowSelf').returns(mockSelfWindow); + sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); + getBoundingClientRectStub = sandbox.stub(boundingClientRectLib, 'getBoundingClientRect'); + percentInViewStub = sandbox.stub(percentInViewLib, 'getViewability').returns(0); + }); + + it('should return frame offset of 0 when not in iframe (topWin === selfWin)', function () { + sandbox.restore(); + sandbox = sinon.createSandbox(); + + const sameDoc = { + getElementById: sandbox.stub().returns({ id: 'test' }), + getElementsByTagName: sandbox.stub().returns([]), + body: { scrollHeight: 2000, offsetHeight: 1800 }, + documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } + }; + const sameWindow = { + innerHeight: 800, + document: sameDoc + }; + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getWindowTop').returns(sameWindow); + sandbox.stub(utils, 'getWindowSelf').returns(sameWindow); + sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); + sandbox.stub(boundingClientRectLib, 'getBoundingClientRect').returns({ + top: 100, bottom: 200, height: 100 + }); + sandbox.stub(percentInViewLib, 'getViewability').returns(0); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should apply iframe offset when running inside a friendly iframe', function () { + iframeTop = 200; + mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should calculate correct distance when element is below viewport with iframe offset', function () { + iframeTop = 500; + mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 600, bottom: 700, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 100); + }); + + it('should calculate negative distance when element is above viewport with iframe offset', function () { + iframeTop = -600; + mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, -400); + }); + + it('should return frame offset of 0 when iframe is not found', function () { + mockTopWindow.document.getElementsByTagName.returns([]); + + getBoundingClientRectStub.returns({ + top: 100, bottom: 200, height: 100 + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should return frame offset of 0 when getElementsByTagName throws', function () { + mockTopWindow.document.getElementsByTagName.throws(new Error('Access denied')); + + getBoundingClientRectStub.returns({ + top: 100, bottom: 200, height: 100 + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should use top window viewport height for distance calculation', function () { + iframeTop = 0; + mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 1200, bottom: 1300, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'test', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 200); + }); + }); + + describe('FIF scenario: Prebid in parent, element in iframe', function () { + let mockElement; + let mockIframeDoc; + let mockIframeWindow; + let mockIframe; + let mockParentDoc; + let mockParentWindow; + let iframeTop; + + beforeEach(function () { + iframeTop = 200; + mockElement = { id: 'iframe-ad-unit' }; + mockIframeWindow = { innerHeight: 400 }; + mockIframeDoc = { + getElementById: sinon.stub().returns(mockElement), + getElementsByTagName: sinon.stub().returns([]) + }; + mockIframe = { + contentDocument: mockIframeDoc, + contentWindow: mockIframeWindow + }; + + sandbox.restore(); + sandbox = sinon.createSandbox(); + + mockIframeDoc.getElementById = sandbox.stub().returns(mockElement); + mockIframeDoc.getElementsByTagName = sandbox.stub().returns([]); + + mockParentDoc = { + getElementById: sandbox.stub().returns(null), + getElementsByTagName: sandbox.stub().returns([mockIframe]), + body: { scrollHeight: 2000, offsetHeight: 1800 }, + documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } + }; + + mockParentWindow = { + innerHeight: 800, + document: mockParentDoc + }; + + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'getWindowTop').returns(mockParentWindow); + sandbox.stub(utils, 'getWindowSelf').returns(mockParentWindow); + sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); + + getBoundingClientRectStub = sandbox.stub(boundingClientRectLib, 'getBoundingClientRect'); + percentInViewStub = sandbox.stub(percentInViewLib, 'getViewability').returns(50); + }); + + it('should find element in iframe document when not in current document', function () { + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'iframe-ad-unit', + sizes: [[300, 250]] + }); + + assert.ok(mockIframeDoc.getElementById.calledWith('iframe-ad-unit')); + assert.strictEqual(result.ElementHeight, 100); + }); + + it('should apply iframe offset when element is in iframe', function () { + iframeTop = 300; + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'iframe-ad-unit', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 0); + }); + + it('should calculate positive distance when element in iframe is below viewport', function () { + iframeTop = 500; + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 400, bottom: 500, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'iframe-ad-unit', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 100); + }); + + it('should calculate negative distance when element in iframe is above viewport', function () { + iframeTop = -500; + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'iframe-ad-unit', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, -300); + }); + + it('should use iframe window for viewability calculation', function () { + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + placementUtils.getPlacementInfo({ + adUnitCode: 'iframe-ad-unit', + sizes: [[300, 250]] + }); + + const viewabilityCall = percentInViewStub.getCall(0); + assert.strictEqual(viewabilityCall.args[1], mockIframeWindow); + }); + + it('should skip cross-origin iframes that throw errors', function () { + const crossOriginIframe = { + get contentDocument() { throw new Error('Blocked by CORS'); }, + get contentWindow() { return null; } + }; + mockParentDoc.getElementsByTagName.returns([crossOriginIframe, mockIframe]); + + getBoundingClientRectStub.callsFake((el) => { + if (el === mockIframe) return { top: iframeTop }; + return { top: 100, bottom: 200, height: 100 }; + }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'iframe-ad-unit', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.ElementHeight, 100); + }); + + it('should return default values when element not found anywhere', function () { + mockIframeDoc.getElementById.returns(null); + + getBoundingClientRectStub.returns({ top: 100, bottom: 200, height: 100 }); + + const placementUtils = getPlacementPositionUtils(); + const result = placementUtils.getPlacementInfo({ + adUnitCode: 'non-existent', + sizes: [[300, 250]] + }); + + assert.strictEqual(result.DistanceToView, 0); + assert.strictEqual(result.ElementHeight, 1); + }); + }); +}); From 957ebcfc76e7129b1d4f777108fafd1cf00d21ee Mon Sep 17 00:00:00 2001 From: Florian Erl <46747754+florianerl@users.noreply.github.com> Date: Thu, 5 Feb 2026 02:14:31 +0000 Subject: [PATCH 017/105] Humansecurity RTD Provider: migrate to TypeScript and optimize token handling (#14362) * - Migrated to TypeScript - Removed hardcoded token injection into ortb2Fragments and delegate to the HUMAN implementation, enabling management of which bidders receive tokens and enhancing monitoring and control of latency and performance - Introduce a cached implementation reference via getImpl() - Add module version query parameter when loading the implementation script - Wire onAuctionInitEvent so the implementation can collect QoS, telemetry and statistics per auction * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * - validate module version parameter in script URL - add HumanSecurityImpl interface --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- modules/humansecurityRtdProvider.js | 180 ---------------- modules/humansecurityRtdProvider.md | 28 +-- modules/humansecurityRtdProvider.ts | 204 ++++++++++++++++++ .../modules/humansecurityRtdProvider_spec.js | 117 +++------- 4 files changed, 245 insertions(+), 284 deletions(-) delete mode 100644 modules/humansecurityRtdProvider.js create mode 100644 modules/humansecurityRtdProvider.ts diff --git a/modules/humansecurityRtdProvider.js b/modules/humansecurityRtdProvider.js deleted file mode 100644 index aeb872beb8d..00000000000 --- a/modules/humansecurityRtdProvider.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * This module adds humansecurity provider to the real time data module - * - * The {@link module:modules/realTimeData} module is required - * The module will inject the HUMAN Security script into the context where Prebid.js is initialized, enriching bid requests with specific data to provide advanced protection against ad fraud and spoofing. - * @module modules/humansecurityRtdProvider - * @requires module:modules/realTimeData - */ - -import { submodule } from '../src/hook.js'; -import { - prefixLog, - mergeDeep, - generateUUID, - getWindowSelf, -} from '../src/utils.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; - -/** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule - * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData - */ - -const SUBMODULE_NAME = 'humansecurity'; -const SCRIPT_URL = 'https://sonar.script.ac/prebid/rtd.js'; - -const { logInfo, logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); - -/** @type {string} */ -let clientId = ''; - -/** @type {boolean} */ -let verbose = false; - -/** @type {string} */ -let sessionId = ''; - -/** @type {Object} */ -let hmnsData = { }; - -/** - * Submodule registration - */ -function main() { - submodule('realTimeData', /** @type {RtdSubmodule} */ ({ - name: SUBMODULE_NAME, - - // - init: (config, userConsent) => { - try { - load(config); - return true; - } catch (err) { - logError('init', err.message); - return false; - } - }, - - getBidRequestData: onGetBidRequestData - })); -} - -/** - * Injects HUMAN Security script on the page to facilitate pre-bid signal collection. - * @param {SubmoduleConfig} config - */ -function load(config) { - // By default, this submodule loads the generic implementation script - // only identified by the referrer information. In the future, if publishers - // want to have analytics where their websites are grouped, they can request - // Client ID from HUMAN, pass it here, and it will enable advanced reporting - clientId = config?.params?.clientId || ''; - if (clientId && (typeof clientId !== 'string' || !/^\w{3,16}$/.test(clientId))) { - throw new Error(`The 'clientId' parameter must be a short alphanumeric string`); - } - - // Load/reset the state - verbose = !!config?.params?.verbose; - sessionId = generateUUID(); - hmnsData = {}; - - // We rely on prebid implementation to get the best domain possible here - // In some cases, it still might be null, though - const refDomain = getRefererInfo().domain || ''; - - // Once loaded, the implementation script will publish an API using - // the session ID value it was given in data attributes - const scriptAttrs = { 'data-sid': sessionId }; - const scriptUrl = `${SCRIPT_URL}?r=${refDomain}${clientId ? `&c=${clientId}` : ''}`; - - loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, onImplLoaded, null, scriptAttrs); -} - -/** - * The callback to loadExternalScript - * Establishes the bridge between this RTD submodule and the loaded implementation - */ -function onImplLoaded() { - // We then get a hold on this script using the knowledge of this session ID - const wnd = getWindowSelf(); - const impl = wnd[`sonar_${sessionId}`]; - if (typeof impl !== 'object' || typeof impl.connect !== 'function') { - verbose && logWarn('onload', 'Unable to access the implementation script'); - return; - } - - // And set up a bridge between the RTD submodule and the implementation. - // The first argument is used to identify the caller. - // The callback might be called multiple times to update the signals - // once more precise information is available. - impl.connect(getGlobal(), onImplMessage); -} - -/** - * The bridge function will be called by the implementation script - * to update the token information or report errors - * @param {Object} msg - */ -function onImplMessage(msg) { - if (typeof msg !== 'object') { - return; - } - - switch (msg.type) { - case 'hmns': { - hmnsData = mergeDeep({}, msg.data || {}); - break; - } - case 'error': { - logError('impl', msg.data || ''); - break; - } - case 'warn': { - verbose && logWarn('impl', msg.data || ''); - break; - } - case 'info': { - verbose && logInfo('impl', msg.data || ''); - break; - } - } -} - -/** - * onGetBidRequestData is called once per auction. - * Update the `ortb2Fragments` object with the data from the injected script. - * - * @param {Object} reqBidsConfigObj - * @param {function} callback - * @param {SubmoduleConfig} config - * @param {UserConsentData} userConsent - */ -function onGetBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - // Add device.ext.hmns to the global ORTB data for all vendors to use - // At the time of writing this submodule, "hmns" is an object defined - // internally by humansecurity, and it currently contains "v1" field - // with a token that contains collected signals about this session. - mergeDeep(reqBidsConfigObj.ortb2Fragments.global, { device: { ext: { hmns: hmnsData } } }); - callback(); -} - -/** - * Exporting local (and otherwise encapsulated to this module) functions - * for testing purposes - */ -export const __TEST__ = { - SUBMODULE_NAME, - SCRIPT_URL, - main, - load, - onImplLoaded, - onImplMessage, - onGetBidRequestData -}; - -main(); diff --git a/modules/humansecurityRtdProvider.md b/modules/humansecurityRtdProvider.md index 6722319cbb5..fceb012e27c 100644 --- a/modules/humansecurityRtdProvider.md +++ b/modules/humansecurityRtdProvider.md @@ -23,7 +23,7 @@ sent within bid requests, and used for bot detection on the backend. * No incremental signals collected beyond existing HUMAN post-bid solution * Offsets negative impact from loss of granularity in IP and User Agent at bid time * Does not expose collected IVT signal to any party who doesn’t otherwise already have access to the same signal collected post-bid -* Does not introduce meaningful latency, as demonstrated in the Latency section +* Does not introduce meaningful latency * Comes at no additional cost to collect IVT signal and make it available at bid time * Leveraged to differentiate the invalid bid requests at device level, and cannot be used to identify a user or a device, thus preserving privacy. @@ -56,8 +56,8 @@ pbjs.setConfig({ }); ``` -It can be optionally parameterized, for example, to include client ID obtained from HUMAN, -should any advanced reporting be needed, or to have verbose output for troubleshooting: +Other parameters can also be provided. For example, a client ID obtained from HUMAN can +optionally be provided, or verbose output can be enabled for troubleshooting purposes: ```javascript pbjs.setConfig({ @@ -79,6 +79,7 @@ pbjs.setConfig({ | :--------------- | :------------ | :------------------------------------------------------------------ |:---------| | `clientId` | String | Should you need advanced reporting, contact [prebid@humansecurity.com](prebid@humansecurity.com) to receive client ID. | No | | `verbose` | Boolean | Only set to `true` if troubleshooting issues. | No | +| `perBidderOptOut` | string[] | Pass any bidder alias to opt-out from per-bidder signal generation. | No | ## Logging, latency and troubleshooting @@ -89,9 +90,6 @@ of type `ERROR`. With `verbose` parameter set to `true`, it may additionally: * Call `logWarning`, resulting in `auctionDebug` events of type `WARNING`, * Call `logInfo` with latency information. - * To observe these messages in console, Prebid.js must be run in - [debug mode](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#debugging) - - either by adding `?pbjs_debug=true` to your page's URL, or by configuring with `pbjs.setConfig({ debug: true });` Example output of the latency information: @@ -139,6 +137,7 @@ There are a few points that are worth being mentioned separately, to avoid confu the ever-evolving nature of the threat landscape without the publishers having to rebuild their Prebid.js frequently. * The signal collection script is also obfuscated, as a defense-in-depth measure in order to complicate tampering by bad actors, as are all similar scripts in the industry, which is something that cannot be accommodated by Prebid.js itself. +* The collected signals are encrypted before they are passed to bid adapters and can only be interpreted by HUMAN backend systems. ## Why is this approach an innovation? @@ -199,10 +198,14 @@ ensuring the value of the inventory. ## FAQ +### Is partnership with HUMAN required to use the submodule? + +No. Using this submodule does not require any prior communication with HUMAN or being a client of HUMAN. +It is free and usage of the submodule doesn’t automatically make a Publisher HUMAN client. + ### Is latency an issue? The HUMAN Security RTD submodule is designed to minimize any latency in the auction within normal SLAs. - ### Do publishers get any insight into how the measurement is judged? Having the The HUMAN Security RTD submodule be part of the prebid process will allow the publisher to have insight @@ -211,13 +214,10 @@ inventory to the buyer. ### How are privacy concerns addressed? -The HUMAN Security RTD submodule seeks to reduce the impacts from signal deprecation that are inevitable without -compromising privacy by avoiding re-identification. Each bid request is enriched with just enough signal -to identify if the traffic is invalid or not. - -By having the The HUMAN Security RTD submodule operate at the Prebid level, data can be controlled -and not as freely passed through the bidstream where it may be accessible to various unknown parties. +The HUMAN Security RTD submodule seeks to reduce the impacts of signal deprecation without compromising privacy. +Each bid request is enriched with just enough signal to identify if the traffic is invalid or not, and these +signals are encrypted before being included in bid requests to prevent misuse. Note: anti-fraud use cases typically have carve outs in laws and regulations to permit data collection essential for effective fraud mitigation, but this does not constitute legal advice and you should -consult your attorney when making data access decisions. +consult your attorney when making data access decisions. \ No newline at end of file diff --git a/modules/humansecurityRtdProvider.ts b/modules/humansecurityRtdProvider.ts new file mode 100644 index 00000000000..96b99eadfb7 --- /dev/null +++ b/modules/humansecurityRtdProvider.ts @@ -0,0 +1,204 @@ +/** + * This module adds humansecurity provider to the real time data module + * + * The {@link module:modules/realTimeData} module is required + * The module will inject the HUMAN Security script into the context where Prebid.js is initialized, enriching bid requests with specific + * data to provide advanced protection against ad fraud and spoofing. + * @module modules/humansecurityRtdProvider + * @requires module:modules/realTimeData + */ +import { submodule } from '../src/hook.js'; +import { prefixLog, generateUUID, getWindowSelf } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getGlobal, PrebidJS } from '../src/prebidGlobal.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { AllConsentData } from '../src/consentHandler.ts'; +import type { RTDProvider, RTDProviderConfig, RtdProviderSpec } from './rtdModule/spec.ts'; +import type { StartAuctionOptions } from '../src/prebid.ts'; +import type { AuctionProperties } from '../src/auction.ts'; + +declare module './rtdModule/spec.ts' { + interface ProviderConfig { + humansecurity: { + params?: { + clientId?: string; + verbose?: boolean; + perBidderOptOut?: string[]; + }; + }; + } +} + +interface HumanSecurityImpl { + connect( + pbjs: PrebidJS, + callback: (m: string) => void | null, + config: RTDProviderConfig<'humansecurity'> + ): void; + + getBidRequestData( + reqBidsConfigObj: StartAuctionOptions, + callback: () => void, + config: RTDProviderConfig<'humansecurity'>, + userConsent: AllConsentData + ): void; + + onAuctionInitEvent( + pbjs: PrebidJS, + auctionDetails: AuctionProperties, + config: RTDProviderConfig<'humansecurity'>, + userConsent: AllConsentData + ): void; +} + +const SUBMODULE_NAME = 'humansecurity' as const; +const SCRIPT_URL = 'https://sonar.script.ac/prebid/rtd.js'; +const MODULE_VERSION = 1; + +const { logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); + +let implRef: HumanSecurityImpl | null = null; +let clientId: string = ''; +let verbose: boolean = false; +let sessionId: string = ''; + +/** + * Injects HUMAN Security script on the page to facilitate pre-bid signal collection. + */ + +const load = (config: RTDProviderConfig<'humansecurity'>) => { + // Load implementation script and pass configuration parameters via data attributes + clientId = config?.params?.clientId || ''; + if (clientId && (typeof clientId !== 'string' || !/^\w{3,16}$/.test(clientId))) { + throw new Error(`The 'clientId' parameter must be a short alphanumeric string`); + } + + // Load/reset the state + verbose = !!config?.params?.verbose; + implRef = null; + sessionId = generateUUID(); + + // Get the best domain possible here, it still might be null + const refDomain = getRefererInfo().domain || ''; + + // Once loaded, the implementation script will publish an API using + // the session ID value it was given in data attributes + const scriptAttrs = { 'data-sid': sessionId }; + const scriptUrl = `${SCRIPT_URL}?r=${refDomain}${clientId ? `&c=${clientId}` : ''}&mv=${MODULE_VERSION}`; + + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, () => onImplLoaded(config), null, scriptAttrs); +} + +/** + * Retrieves the implementation object created by the loaded script + * using the session ID as a key + */ + +const getImpl = () => { + // Use cached reference if already resolved + if (implRef && typeof implRef === 'object' && typeof implRef.connect === 'function') return implRef; + + // Attempt to resolve from window by session ID + const wnd = getWindowSelf(); + const impl: HumanSecurityImpl = wnd[`sonar_${sessionId}`]; + + if (typeof impl !== 'object' || typeof impl.connect !== 'function') { + verbose && logWarn('onload', 'Unable to access the implementation script'); + return; + } + implRef = impl; + return impl; +} + +/** + * The callback to loadExternalScript + * Establishes the bridge between this RTD submodule and the loaded implementation + */ + +const onImplLoaded = (config: RTDProviderConfig<'humansecurity'>) => { + const impl = getImpl(); + if (!impl) return; + + // And set up a bridge between the RTD submodule and the implementation. + impl.connect(getGlobal(), null, config); +} + +/** + * The bridge function will be called by the implementation script + * to update the token information or report errors + */ + +/** + * https://docs.prebid.org/dev-docs/add-rtd-submodule.html#getbidrequestdata + */ + +const getBidRequestData = ( + reqBidsConfigObj: StartAuctionOptions, + callback: () => void, + config: RtdProviderSpec<'humansecurity'>, + userConsent: AllConsentData +) => { + const impl = getImpl(); + if (!impl || typeof impl.getBidRequestData !== 'function') { + // Implementation not available; continue auction by invoking the callback. + callback(); + return; + } + + impl.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); +} + +/** + * Event hooks + * https://docs.prebid.org/dev-docs/add-rtd-submodule.html#using-event-listeners + */ + +const onAuctionInitEvent = (auctionDetails: AuctionProperties, config: RTDProviderConfig<'humansecurity'>, userConsent: AllConsentData) => { + const impl = getImpl(); + if (!impl || typeof impl.onAuctionInitEvent !== 'function') return; + impl.onAuctionInitEvent(getGlobal(), auctionDetails, config, userConsent); +} + +/** + * Submodule registration + */ + +type RtdProviderSpecWithHooks

= RtdProviderSpec

& { + onAuctionInitEvent?: (auctionDetails: AuctionProperties, config: RTDProviderConfig

, userConsent: AllConsentData) => void; +}; + +const subModule: RtdProviderSpecWithHooks<'humansecurity'> = ({ + name: SUBMODULE_NAME, + init: (config, _userConsent) => { + try { + load(config); + return true; + } catch (err) { + const message = (err && typeof err === 'object' && 'message' in err) + ? (err as any).message + : String(err); + logError('init', message); + return false; + } + }, + getBidRequestData, + onAuctionInitEvent, +}); + +const registerSubModule = () => { submodule('realTimeData', subModule); } +registerSubModule(); + +/** + * Exporting local (and otherwise encapsulated to this module) functions + * for testing purposes + */ + +export const __TEST__ = { + SUBMODULE_NAME, + SCRIPT_URL, + main: registerSubModule, + load, + onImplLoaded, + getBidRequestData +}; diff --git a/test/spec/modules/humansecurityRtdProvider_spec.js b/test/spec/modules/humansecurityRtdProvider_spec.js index c6ad163c544..2104ffc6edd 100644 --- a/test/spec/modules/humansecurityRtdProvider_spec.js +++ b/test/spec/modules/humansecurityRtdProvider_spec.js @@ -10,10 +10,7 @@ const { SUBMODULE_NAME, SCRIPT_URL, main, - load, - onImplLoaded, - onImplMessage, - onGetBidRequestData + load } = __TEST__; describe('humansecurity RTD module', function () { @@ -23,20 +20,20 @@ describe('humansecurity RTD module', function () { const sonarStubId = `sonar_${stubUuid}`; const stubWindow = { [sonarStubId]: undefined }; - beforeEach(function() { + beforeEach(function () { sandbox = sinon.createSandbox(); sandbox.stub(utils, 'getWindowSelf').returns(stubWindow); sandbox.stub(utils, 'generateUUID').returns(stubUuid); sandbox.stub(refererDetection, 'getRefererInfo').returns({ domain: 'example.com' }); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); describe('Initialization step', function () { let sandbox2; let connectSpy; - beforeEach(function() { + beforeEach(function () { sandbox2 = sinon.createSandbox(); connectSpy = sandbox.spy(); // Once the impl script is loaded, it registers the API using session ID @@ -46,6 +43,17 @@ describe('humansecurity RTD module', function () { sandbox2.restore(); }); + it('should connect to the implementation script once it loads', function () { + load({}); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + const callback = loadExternalScriptStub.getCall(0).args[3]; + expect(callback).to.be.a('function'); + const args = connectSpy.getCall(0).args; + expect(args[0]).to.haveOwnProperty('cmd'); // pbjs global + expect(args[0]).to.haveOwnProperty('que'); + }); + it('should accept valid configurations', function () { // Default configuration - empty expect(() => load({})).to.not.throw(); @@ -62,14 +70,19 @@ describe('humansecurity RTD module', function () { }); it('should insert implementation script', () => { - load({ }); + load({}); expect(loadExternalScriptStub.calledOnce).to.be.true; const args = loadExternalScriptStub.getCall(0).args; - expect(args[0]).to.be.equal(`${SCRIPT_URL}?r=example.com`); + expect(args[0]).to.include(`${SCRIPT_URL}?r=example.com`); + const mvMatch = args[0].match(/[?&]mv=([^&]+)/); + expect(mvMatch).to.not.equal(null); + const mvValue = Number(mvMatch[1]); + expect(Number.isFinite(mvValue)).to.equal(true); + expect(mvValue).to.be.greaterThan(0); expect(args[2]).to.be.equal(SUBMODULE_NAME); - expect(args[3]).to.be.equal(onImplLoaded); + expect(args[3]).to.be.a('function'); expect(args[4]).to.be.equal(null); expect(args[5]).to.be.deep.equal({ 'data-sid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }); }); @@ -80,90 +93,14 @@ describe('humansecurity RTD module', function () { expect(loadExternalScriptStub.calledOnce).to.be.true; const args = loadExternalScriptStub.getCall(0).args; - expect(args[0]).to.be.equal(`${SCRIPT_URL}?r=example.com&c=customer123`); - }); - - it('should connect to the implementation script once it loads', function () { - load({ }); - - expect(loadExternalScriptStub.calledOnce).to.be.true; - expect(connectSpy.calledOnce).to.be.true; - - const args = connectSpy.getCall(0).args; - expect(args[0]).to.haveOwnProperty('cmd'); // pbjs global - expect(args[0]).to.haveOwnProperty('que'); - expect(args[1]).to.be.equal(onImplMessage); - }); - }); - - describe('Bid enrichment step', function () { - const hmnsData = { 'v1': 'sometoken' }; - - let sandbox2; - let callbackSpy; - let reqBidsConfig; - beforeEach(function() { - sandbox2 = sinon.createSandbox(); - callbackSpy = sandbox2.spy(); - reqBidsConfig = { ortb2Fragments: { bidder: {}, global: {} } }; - }); - afterEach(function () { - sandbox2.restore(); - }); - - it('should add empty device.ext.hmns to global ortb2 when data is yet to be received from the impl script', () => { - load({ }); - - onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); - - expect(callbackSpy.calledOnce).to.be.true; - expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); - expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); - expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals({}); - }); - - it('should add the default device.ext.hmns to global ortb2 when no "hmns" data was yet received', () => { - load({ }); - - onImplMessage({ type: 'info', data: 'not a hmns message' }); - onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); - - expect(callbackSpy.calledOnce).to.be.true; - expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); - expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); - expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals({}); - }); - - it('should add device.ext.hmns with received tokens to global ortb2 when the data was received', () => { - load({ }); - - onImplMessage({ type: 'hmns', data: hmnsData }); - onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); - - expect(callbackSpy.calledOnce).to.be.true; - expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); - expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); - expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals(hmnsData); - }); - - it('should update device.ext.hmns with new data', () => { - load({ }); - - onImplMessage({ type: 'hmns', data: { 'v1': 'should be overwritten' } }); - onImplMessage({ type: 'hmns', data: hmnsData }); - onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); - - expect(callbackSpy.calledOnce).to.be.true; - expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); - expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); - expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals(hmnsData); + expect(args[0]).to.include(`${SCRIPT_URL}?r=example.com&c=customer123`); }); }); - describe('Sumbodule execution', function() { + describe('Submodule execution', function () { let sandbox2; let submoduleStub; - beforeEach(function() { + beforeEach(function () { sandbox2 = sinon.createSandbox(); submoduleStub = sandbox2.stub(hook, 'submodule'); }); @@ -203,7 +140,7 @@ describe('humansecurity RTD module', function () { it('should commence initialization on default initialization', function () { const { init } = getModule(); - expect(init({ })).to.equal(true); + expect(init({})).to.equal(true); expect(loadExternalScriptStub.calledOnce).to.be.true; }); }); From 439b0aaa1a7fa2c8024916208c61b849663392f1 Mon Sep 17 00:00:00 2001 From: Luca Corbo Date: Thu, 5 Feb 2026 03:14:59 +0100 Subject: [PATCH 018/105] WURFL RTD: update module documentation (#14364) * WURFL RTD: update module documentation * WURFL RTD: update module documentation --- modules/wurflRtdProvider.md | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/modules/wurflRtdProvider.md b/modules/wurflRtdProvider.md index 30651a6ddea..4bc088303e0 100644 --- a/modules/wurflRtdProvider.md +++ b/modules/wurflRtdProvider.md @@ -8,10 +8,11 @@ ## Description -The WURFL RTD module enriches the OpenRTB 2.0 device data with [WURFL data](https://www.scientiamobile.com/wurfl-js-business-edition-at-the-intersection-of-javascript-and-enterprise/). -The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilities like `is_mobile`, `complete_device_name` and `form_factor`, and the `wurfl_id`. +The WURFL RTD module enriches Prebid.js bid requests with comprehensive device detection data. -For a more detailed analysis bidders can subscribe to detect iPhone and iPad models and receive additional [WURFL device capabilities](https://www.scientiamobile.com/capabilities/?products%5B%5D=wurfl-js). +The WURFL RTD module relies on localStorage caching and local client-side detection, providing instant device enrichment on every page load. + +The module enriches `ortb2.device` with complete device information and adds extended WURFL capabilities to `device.ext.wurfl`, ensuring all bidder adapters have immediate access to enriched device data. **Note:** This module loads a dynamically generated JavaScript from prebid.wurflcloud.com @@ -34,10 +35,8 @@ Use `setConfig` to instruct Prebid.js to initilize the WURFL RTD module, as spec This module is configured as part of the `realTimeData.dataProviders` ```javascript -var TIMEOUT = 1000; pbjs.setConfig({ realTimeData: { - auctionDelay: TIMEOUT, dataProviders: [ { name: "wurfl", @@ -49,16 +48,14 @@ pbjs.setConfig({ ### Parameters -| Name | Type | Description | Default | -| :------------------ | :------ | :--------------------------------------------------------------- | :------------- | -| name | String | Real time data module name | Always 'wurfl' | -| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | -| params | Object | | | -| params.altHost | String | Alternate host to connect to WURFL.js | | -| params.abTest | Boolean | Enable A/B testing mode | `false` | -| params.abName | String | A/B test name identifier | `'unknown'` | -| params.abSplit | Number | Fraction of users in treatment group (0-1) | `0.5` | -| params.abExcludeLCE | Boolean | Don't apply A/B testing to LCE bids | `true` | +| Name | Type | Description | Default | +| :------------- | :------ | :----------------------------------------- | :------------- | +| name | String | Real time data module name | Always 'wurfl' | +| params | Object | | | +| params.altHost | String | Alternate host to connect to WURFL.js | | +| params.abTest | Boolean | Enable A/B testing mode | `false` | +| params.abName | String | A/B test name identifier | `'unknown'` | +| params.abSplit | Number | Fraction of users in treatment group (0-1) | `0.5` | ### A/B Testing @@ -67,15 +64,13 @@ The WURFL RTD module supports A/B testing to measure the impact of WURFL enrichm ```javascript pbjs.setConfig({ realTimeData: { - auctionDelay: 1000, dataProviders: [ { name: "wurfl", - waitForIt: true, params: { abTest: true, - abName: "pub_test_sept23", - abSplit: 0.5, // 50% treatment, 50% control + abName: "pub_test", + abSplit: 0.75, // 75% treatment, 25% control }, }, ], From 16c5f973bb9b01f0b97f9130d257b400c7c910c7 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 4 Feb 2026 21:25:54 -0500 Subject: [PATCH 019/105] Change humansecurityRtdProvider.js to .ts extension --- plugins/eslint/approvedLoadExternalScriptPaths.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/eslint/approvedLoadExternalScriptPaths.js b/plugins/eslint/approvedLoadExternalScriptPaths.js index 8a2d2d2a4d5..95552535439 100644 --- a/plugins/eslint/approvedLoadExternalScriptPaths.js +++ b/plugins/eslint/approvedLoadExternalScriptPaths.js @@ -14,7 +14,7 @@ const APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS = [ 'modules/brandmetricsRtdProvider.js', 'modules/cleanioRtdProvider.js', 'modules/humansecurityMalvDefenseRtdProvider.js', - 'modules/humansecurityRtdProvider.js', + 'modules/humansecurityRtdProvider.ts', 'modules/confiantRtdProvider.js', 'modules/contxtfulRtdProvider.js', 'modules/hadronRtdProvider.js', From 8a5fb8bcccb089011281f8f3e5b747c6d5b2422c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bendeg=C3=BAz=20=C3=81cs?= <30595431+acsbendi@users.noreply.github.com> Date: Thu, 5 Feb 2026 03:29:02 +0100 Subject: [PATCH 020/105] Kobler bid adapter: pass adunitcode in bid request. (#14392) * Page view ID. * Page view ID. * Removed console.log. * Removed unused import. * Improved example. * Fixed some tests. * Kobler bid adapter: pass adunitcode in bid request. --- modules/koblerBidAdapter.js | 7 ++++- test/spec/modules/koblerBidAdapter_spec.js | 30 +++++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 2b0c55b2fb9..0eb6a74abfe 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -204,7 +204,12 @@ function buildOpenRtbImpObject(validBidRequest) { }, bidfloor: floorInfo.floor, bidfloorcur: floorInfo.currency, - pmp: buildPmpObject(validBidRequest) + pmp: buildPmpObject(validBidRequest), + ext: { + prebid: { + adunitcode: validBidRequest.adUnitCode + } + } }; } diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 4e70ac6f9f3..8b771e3eef7 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -26,9 +26,9 @@ function createBidderRequest(auctionId, timeout, pageUrl, gdprVendorData = {}, p }; } -function createValidBidRequest(params, bidId, sizes) { +function createValidBidRequest(params, bidId, sizes, adUnitCode) { const validBidRequest = { - adUnitCode: 'adunit-code', + adUnitCode: adUnitCode || 'adunit-code', bidId: bidId || '22c4871113f461', bidder: 'kobler', bidderRequestId: '15246a574e859f', @@ -451,7 +451,8 @@ describe('KoblerAdapter', function () { dealIds: ['623472534328234'] }, '953ee65d-d18a-484f-a840-d3056185a060', - [[400, 600]] + [[400, 600]], + 'ad-unit-1' ), createValidBidRequest( { @@ -459,12 +460,14 @@ describe('KoblerAdapter', function () { dealIds: ['92368234753283', '263845832942'] }, '8320bf79-9d90-4a17-87c6-5d505706a921', - [[400, 500], [200, 250], [300, 350]] + [[400, 500], [200, 250], [300, 350]], + 'ad-unit-2' ), createValidBidRequest( undefined, 'd0de713b-32e3-4191-a2df-a007f08ffe72', - [[800, 900]] + [[800, 900]], + 'ad-unit-3' ) ]; const bidderRequest = createBidderRequest( @@ -520,6 +523,11 @@ describe('KoblerAdapter', function () { id: '623472534328234' } ] + }, + ext: { + prebid: { + adunitcode: 'ad-unit-1' + } } }, { @@ -553,6 +561,11 @@ describe('KoblerAdapter', function () { id: '263845832942' } ] + }, + ext: { + prebid: { + adunitcode: 'ad-unit-2' + } } }, { @@ -569,7 +582,12 @@ describe('KoblerAdapter', function () { }, bidfloor: 0, bidfloorcur: 'USD', - pmp: {} + pmp: {}, + ext: { + prebid: { + adunitcode: 'ad-unit-3' + } + } } ], device: { From 3d91b02505a4372a156bbd03d4061558a2a0047c Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 4 Feb 2026 18:33:37 -0800 Subject: [PATCH 021/105] percentInView: fix bug where viewability calculation is inaccurate inside friendly iframes (#14414) * percentInView: fix bug where viewability calculation is inaccurate inside friendly iframes * use boundingClientRect --- libraries/percentInView/percentInView.js | 39 +++++++++++++++-- test/spec/libraries/percentInView_spec.js | 40 +++++++++++++++++ test/spec/modules/connatixBidAdapter_spec.js | 43 ++++++++++++------- test/spec/modules/marsmediaBidAdapter_spec.js | 11 +++-- test/spec/modules/omsBidAdapter_spec.js | 11 ++--- test/spec/modules/onomagicBidAdapter_spec.js | 12 +++--- test/test_deps.js | 8 ++++ 7 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 test/spec/libraries/percentInView_spec.js diff --git a/libraries/percentInView/percentInView.js b/libraries/percentInView/percentInView.js index 27148e40941..5b9dfa10503 100644 --- a/libraries/percentInView/percentInView.js +++ b/libraries/percentInView/percentInView.js @@ -1,6 +1,29 @@ import { getWinDimensions, inIframe } from '../../src/utils.js'; import { getBoundingClientRect } from '../boundingClientRect/boundingClientRect.js'; +/** + * return the offset between the given window's viewport and the top window's. + */ +export function getViewportOffset(win = window) { + let x = 0; + let y = 0; + try { + while (win?.frameElement != null) { + const rect = getBoundingClientRect(win.frameElement); + x += rect.left; + y += rect.top; + win = win.parent; + } + } catch (e) { + // offset cannot be calculated as some parents are cross-frame + // fallback to 0,0 + x = 0; + y = 0; + } + + return {x, y}; +} + export function getBoundingBox(element, {w, h} = {}) { let {width, height, left, top, right, bottom, x, y} = getBoundingClientRect(element); @@ -44,14 +67,24 @@ function getIntersectionOfRects(rects) { export const percentInView = (element, {w, h} = {}) => { const elementBoundingBox = getBoundingBox(element, {w, h}); - const { innerHeight, innerWidth } = getWinDimensions(); + // when in an iframe, the bounding box is relative to the iframe's viewport + // since we are intersecting it with the top window's viewport, attempt to + // compensate for the offset between them + + const offset = getViewportOffset(element?.ownerDocument?.defaultView); + elementBoundingBox.left += offset.x; + elementBoundingBox.right += offset.x; + elementBoundingBox.top += offset.y; + elementBoundingBox.bottom += offset.y; + + const dims = getWinDimensions(); // Obtain the intersection of the element and the viewport const elementInViewBoundingBox = getIntersectionOfRects([{ left: 0, top: 0, - right: innerWidth, - bottom: innerHeight + right: dims.document.documentElement.clientWidth, + bottom: dims.document.documentElement.clientHeight }, elementBoundingBox]); let elementInViewArea, elementTotalArea; diff --git a/test/spec/libraries/percentInView_spec.js b/test/spec/libraries/percentInView_spec.js new file mode 100644 index 00000000000..92c44edace8 --- /dev/null +++ b/test/spec/libraries/percentInView_spec.js @@ -0,0 +1,40 @@ +import {getViewportOffset} from '../../../libraries/percentInView/percentInView.js'; + +describe('percentInView', () => { + describe('getViewportOffset', () => { + function mockWindow(offsets = []) { + let win, leaf, child; + win = leaf = {}; + for (const [x, y] of offsets) { + win.frameElement = { + getBoundingClientRect() { + return {left: x, top: y}; + } + }; + child = win; + win = {}; + child.parent = win; + } + return leaf; + } + it('returns 0, 0 for the top window', () => { + expect(getViewportOffset(mockWindow())).to.eql({x: 0, y: 0}); + }); + + it('returns frame offset for a direct child', () => { + expect(getViewportOffset(mockWindow([[10, 20]]))).to.eql({x: 10, y: 20}); + }); + it('returns cumulative offests for descendants', () => { + expect(getViewportOffset(mockWindow([[10, 20], [20, 30]]))).to.eql({x: 30, y: 50}); + }); + it('does not choke when parent is not accessible', () => { + const win = mockWindow([[10, 20]]); + Object.defineProperty(win, 'frameElement', { + get() { + throw new Error(); + } + }); + expect(getViewportOffset(win)).to.eql({x: 0, y: 0}); + }); + }); +}); diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 35e60c403af..aa9bdac75bd 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -142,10 +142,12 @@ describe('connatixBidAdapter', function () { let element; let getBoundingClientRectStub; let topWinMock; + let sandbox; beforeEach(() => { + sandbox = sinon.createSandbox(); element = document.createElement('div'); - getBoundingClientRectStub = sinon.stub(element, 'getBoundingClientRect'); + getBoundingClientRectStub = sandbox.stub(element, 'getBoundingClientRect'); topWinMock = { document: { @@ -154,10 +156,18 @@ describe('connatixBidAdapter', function () { innerWidth: 800, innerHeight: 600 }; + sandbox.stub(winDimensions, 'getWinDimensions').callsFake(() => ({ + document: { + documentElement: { + clientWidth: topWinMock.innerWidth, + clientHeight: topWinMock.innerHeight + } + } + })); }); afterEach(() => { - getBoundingClientRectStub.restore(); + sandbox.restore(); }); it('should return 0 if the document is not visible', () => { @@ -180,25 +190,18 @@ describe('connatixBidAdapter', function () { it('should return the correct percentage if the element is partially in view', () => { const boundingBox = { left: 700, top: 500, right: 900, bottom: 700, width: 200, height: 200 }; getBoundingClientRectStub.returns(boundingBox); - const getWinDimensionsStub = sinon.stub(winDimensions, 'getWinDimensions'); - getWinDimensionsStub.returns({ innerWidth: topWinMock.innerWidth, innerHeight: topWinMock.innerHeight}); - const viewability = connatixGetViewability(element, topWinMock); expect(viewability).to.equal(25); // 100x100 / 200x200 = 0.25 -> 25% - getWinDimensionsStub.restore(); }); it('should return 0% if the element is not in view', () => { - const getWinDimensionsStub = sinon.stub(winDimensions, 'getWinDimensions'); - getWinDimensionsStub.returns({ innerWidth: topWinMock.innerWidth, innerHeight: topWinMock.innerHeight}); const boundingBox = { left: 900, top: 700, right: 1100, bottom: 900, width: 200, height: 200 }; getBoundingClientRectStub.returns(boundingBox); const viewability = connatixGetViewability(element, topWinMock); expect(viewability).to.equal(0); - getWinDimensionsStub.restore(); }); it('should use provided width and height if element dimensions are zero', () => { @@ -218,10 +221,12 @@ describe('connatixBidAdapter', function () { let topWinMock; let querySelectorStub; let getElementByIdStub; + let sandbox; beforeEach(() => { + sandbox = sinon.createSandbox(); element = document.createElement('div'); - getBoundingClientRectStub = sinon.stub(element, 'getBoundingClientRect'); + getBoundingClientRectStub = sandbox.stub(element, 'getBoundingClientRect'); topWinMock = { document: { @@ -231,14 +236,22 @@ describe('connatixBidAdapter', function () { innerHeight: 600 }; - querySelectorStub = sinon.stub(window.top.document, 'querySelector'); - getElementByIdStub = sinon.stub(document, 'getElementById'); + querySelectorStub = sandbox.stub(window.top.document, 'querySelector'); + getElementByIdStub = sandbox.stub(document, 'getElementById'); + sandbox.stub(winDimensions, 'getWinDimensions').callsFake(() => ( + { + document: { + documentElement: { + clientWidth: topWinMock.innerWidth, + clientHeight: topWinMock.innerHeight + } + } + } + )); }); afterEach(() => { - getBoundingClientRectStub.restore(); - querySelectorStub.restore(); - getElementByIdStub.restore(); + sandbox.restore(); }); it('should return 100% viewability when the element is fully within view and has a valid viewabilityContainerIdentifier', () => { diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index 30c68601767..c8a0109a94b 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -32,13 +32,15 @@ describe('marsmedia adapter tests', function () { }; win = { document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600 + } }, location: { href: 'http://location' }, - innerWidth: 800, - innerHeight: 600 }; this.defaultBidderRequest = { 'refererInfo': { @@ -506,6 +508,8 @@ describe('marsmedia adapter tests', function () { context('when element is fully in view', function() { it('returns 100', function() { + sandbox.stub(internal, 'getWindowTop').returns(win); + resetWinDimensions(); Object.assign(element, { width: 600, height: 400 }); const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(request.data); @@ -530,7 +534,6 @@ describe('marsmedia adapter tests', function () { const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(request.data); expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); - internal.getWindowTop.restore(); }); }); diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index f1f7df46843..3b9c0779fb0 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -34,7 +34,11 @@ describe('omsBidAdapter', function () { }; win = { document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600, + } }, location: { href: "http:/location" @@ -80,6 +84,7 @@ describe('omsBidAdapter', function () { sandbox = sinon.createSandbox(); sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(winDimensions, 'getWinDimensions').returns(win); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); @@ -347,8 +352,6 @@ describe('omsBidAdapter', function () { context('when element is partially in view', function () { it('returns percentage', function () { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, {width: 800, height: 800}); const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -358,8 +361,6 @@ describe('omsBidAdapter', function () { context('when width or height of the element is zero', function () { it('try to use alternative values', function () { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, {width: 0, height: 0}); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index 6b4d44f49ed..d043363b34d 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -34,9 +34,12 @@ describe('onomagicBidAdapter', function() { }; win = { document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600 + } }, - innerWidth: 800, innerHeight: 600 }; @@ -57,6 +60,7 @@ describe('onomagicBidAdapter', function() { }]; sandbox = sinon.createSandbox(); + sandbox.stub(winDimensions, 'getWinDimensions').returns(win); sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); @@ -162,8 +166,6 @@ describe('onomagicBidAdapter', function() { context('when element is partially in view', function() { it('returns percentage', function() { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, { width: 800, height: 800 }); const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -173,8 +175,6 @@ describe('onomagicBidAdapter', function() { context('when width or height of the element is zero', function() { it('try to use alternative values', function() { - const getWinDimensionsStub = sandbox.stub(winDimensions, 'getWinDimensions') - getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); diff --git a/test/test_deps.js b/test/test_deps.js index 7047e775d9c..5f0ab890035 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -41,6 +41,14 @@ sinon.createFakeServerWithClock = fakeServerWithClock.create.bind(fakeServerWith localStorage.clear(); +if (window.frameElement != null) { + // sometimes (e.g. chrome headless) the tests run in an iframe that is offset from the top window + // other times (e.g. browser debug page) they run in the top window + // this can cause inconsistencies with the percentInView libraries; if we are in a frame, + // fake the same dimensions as the top window + window.frameElement.getBoundingClientRect = () => window.top.getBoundingClientRect(); +} + require('test/helpers/global_hooks.js'); require('test/helpers/consentData.js'); require('test/helpers/prebidGlobal.js'); From 39455b53fdb322a43c8efa69311e9e247d4aa88c Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 5 Feb 2026 20:47:36 +0100 Subject: [PATCH 022/105] New module: Shaping rules (#14079) * initial commit * storeSplits method * unit tests * remove test * removing storing & extract merging ortb2 * lint * adding default handling * lint * merge fpd cache * review fixes * refactoring, bug fixing, tests * lint * extending bidPrice schema function * removing invalid example * session random * rename * module name * random per auction * modifies some schema function to return value instead of condition result * extra schema evaluators * json fix * evaluating rules per auction * evaluating rules per auction * static config * auctionId * safe guards * registering activities only once * expose configuration type; update integ example rules json URL * evaluating rules fix * fixing model group selection --------- Co-authored-by: Demetrio Girardi Co-authored-by: Patrick McCann --- integrationExamples/shapingRules/rules.json | 151 +++ .../shapingRules/shapingRulesModule.html | 168 ++++ modules/rules/index.ts | 506 +++++++++++ src/activities/activities.js | 5 + src/adapterManager.ts | 82 +- src/auction.ts | 17 +- src/auctionIndex.js | 1 + test/spec/modules/rules_spec.js | 856 ++++++++++++++++++ 8 files changed, 1756 insertions(+), 30 deletions(-) create mode 100644 integrationExamples/shapingRules/rules.json create mode 100644 integrationExamples/shapingRules/shapingRulesModule.html create mode 100644 modules/rules/index.ts create mode 100644 test/spec/modules/rules_spec.js diff --git a/integrationExamples/shapingRules/rules.json b/integrationExamples/shapingRules/rules.json new file mode 100644 index 00000000000..3be6ecfb574 --- /dev/null +++ b/integrationExamples/shapingRules/rules.json @@ -0,0 +1,151 @@ +{ + "enabled": true, + "generateRulesFromBidderConfig": true, + "timestamp": "20250131 00:00:00", + "ruleSets": [ + { + "stage": "processed-auction-request", + "name": "exclude-in-jpn", + "version": "1234", + "modelGroups": [ + { + "weight": 98, + "analyticsKey": "experiment-name", + "version": "4567", + "schema": [ + { "function": "deviceCountryIn", "args": [["JPN"]] } + ], + "default": [], + "rules": [ + { + "conditions": ["true"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { "bidders": ["testBidder"], "seatnonbid": 203, "analyticsValue": "rmjpn" }, + { "bidders": ["bidderD"], "seatnonbid": 203, "ifSyncedId": false, "analyticsValue": "rmjpn" } + ] + } + ] + }, + { + "conditions": [], + "results": [] + } + ] + }, + { + "weight": 2, + "analyticsKey": "experiment-name-2", + "version": "4567", + "schema": [ + { "function": "adUnitCode" } + ], + "default": [], + "rules": [ + { + "conditions": ["adUnit-0000"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { "bidders": ["testBidder"], "seatnonbid": 203, "analyticsValue": "rmjpn" }, + { "bidders": ["bidderD"], "seatnonbid": 203, "ifSyncedId": false, "analyticsValue": "rmjpn" } + ] + } + ] + } + ] + } + ] + }, + { + "stage": "processed-auction", + "modelGroups": [ + { + "schema": [{ "function": "percent", "args": [5] }], + "analyticsKey": "bidderC-testing", + "default": [ + { + "function": "logAtag", + "args": { "analyticsValue": "default-allow" } + } + ], + "rules": [ + { + "conditions": ["false"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { + "bidders": ["bidderC"], + "seatnonbid": 203, + "analyticsValue": "excluded" + } + ] + } + ] + } + ] + } + ] + }, + { + "stage": "processed-auction", + "modelGroups": [ + { + "schema": [ + { + "function": "deviceCountry", "args": ["USA"] + } + ], + "rules": [ + { + "conditions": ["true"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { + "bidders": ["bidderM", "bidderN", "bidderO", "bidderP"], + "seatNonBid": 203 + } + ] + } + ] + } + ] + } + ] + }, + { + "stage": "processed-auction", + "modelGroups": [ + { + "schema": [ + { "function": "deviceCountryIn", "args": [["USA", "CAN"]] } + ], + "analyticsKey": "bidder-yaml", + "rules": [ + { + "conditions": ["false"], + "results": [ + { + "function": "excludeBidders", + "args": [ + { + "bidders": ["bidderX"], + "seatNonBid": 203 + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/integrationExamples/shapingRules/shapingRulesModule.html b/integrationExamples/shapingRules/shapingRulesModule.html new file mode 100644 index 00000000000..8843348e731 --- /dev/null +++ b/integrationExamples/shapingRules/shapingRulesModule.html @@ -0,0 +1,168 @@ + + + Prebid Test Bidder Example + + + + +

Prebid Test Bidder Example

+
Banner ad
+ + + diff --git a/modules/rules/index.ts b/modules/rules/index.ts new file mode 100644 index 00000000000..a6667982b5e --- /dev/null +++ b/modules/rules/index.ts @@ -0,0 +1,506 @@ +import { setLabels } from "../../libraries/analyticsAdapter/AnalyticsAdapter.ts"; +import { timeoutQueue } from "../../libraries/timeoutQueue/timeoutQueue.ts"; +import { ACTIVITY_ADD_BID_RESPONSE, ACTIVITY_FETCH_BIDS } from "../../src/activities/activities.js"; +import { MODULE_TYPE_BIDDER } from "../../src/activities/modules.ts"; +import { ACTIVITY_PARAM_COMPONENT_NAME, ACTIVITY_PARAM_COMPONENT_TYPE } from "../../src/activities/params.js"; +import { registerActivityControl } from "../../src/activities/rules.js"; +import { ajax } from "../../src/ajax.ts"; +import { AuctionIndex } from "../../src/auctionIndex.js"; +import { auctionManager } from "../../src/auctionManager.js"; +import { config } from "../../src/config.ts"; +import { getHook } from "../../src/hook.ts"; +import { generateUUID, logInfo, logWarn } from "../../src/utils.ts"; +import { timedAuctionHook } from "../../src/utils/perfMetrics.ts"; + +/** + * Configuration interface for the shaping rules module. + */ +interface ShapingRulesConfig { + /** + * Endpoint configuration for fetching rules from a remote server. + * If not provided, rules must be provided statically via the `rules` property. + */ + endpoint?: { + /** URL endpoint to fetch rules configuration from */ + url: string; + /** HTTP method to use for fetching rules (currently only 'GET' is supported) */ + method: string; + }; + /** + * Static rules configuration object. + * If provided, rules will be used directly without fetching from endpoint. + * Takes precedence over endpoint configuration. + */ + rules?: RulesConfig; + /** + * Delay in milliseconds to wait for rules to be fetched before starting the auction. + * If rules are not loaded within this delay, the auction will proceed anyway. + * Default: 0 (no delay) + */ + auctionDelay?: number; + /** + * Custom schema evaluator functions to extend the default set of evaluators. + * Keys are function names, values are evaluator functions that take args and context, + * and return a function that evaluates to a value when called. + */ + extraSchemaEvaluators?: { + [key: string]: (args: any[], context: any) => () => any; + }; +} + +/** + * Schema function definition used to compute values. + */ +interface ModelGroupSchema { + /** Function name inside the schema */ + function: string; + /** Arguments for the schema function */ + args: any[]; +} + +/** + * Model group configuration for A/B testing with different rule configurations. + * Only one object within the group is chosen based on weight. + */ +interface ModelGroup { + /** Determines selection probability; only one object within the group is chosen */ + weight: number; + /** Indicates whether this model group is selected (set automatically based on weight) */ + selected: boolean; + /** Optional key used to produce aTags, identifying experiments or optimization targets */ + analyticsKey: string; + /** Version identifier for analytics */ + version: string; + /** + * Optional array of functions used to compute values. + * Without it, only the default rule is applied. + */ + schema: ModelGroupSchema[]; + /** + * Optional rule array; if absent, only the default rule is used. + * Each rule has conditions that must be met and results that are triggered. + */ + rules: [{ + /** Conditions that must be met for the rule to apply */ + condition: string[]; + /** Resulting actions triggered when conditions are met */ + results: [ + { + /** Function defining the result action */ + function: string; + /** Arguments for the result function */ + args: any[]; + } + ]; + }]; + /** + * Default results object used if errors occur or when no schema or rules are defined. + * Exists outside the rules array for structural clarity. + */ + default?: Array<{ + /** Function defining the default result action */ + function: string; + /** Arguments for the default result function */ + args: any; + }>; +} + +/** + * Independent set of rules that can be applied to a specific stage of the auction. + */ +interface RuleSet { + /** Human-readable name of the ruleset */ + name: string; + /** + * Indicates which module stage the ruleset applies to. + * Can be either `processed-auction-request` or `processed-auction` + */ + stage: string; + /** Version identifier for the ruleset */ + version: string; + /** + * Optional timestamp of the last update (ISO 8601 format: `YYYY-MM-DDThh:mm:ss[.sss][Z or ±hh:mm]`) + */ + timestamp?: string; + /** + * One or more model groups for A/B testing with different rule configurations. + * Allows A/B testing with different rule configurations. + */ + modelGroups: ModelGroup[]; +} + +/** + * Main configuration object for the shaping rules module. + */ +interface RulesConfig { + /** Version identifier for the rules configuration */ + version: string; + /** One or more independent sets of rules */ + ruleSets: RuleSet[]; + /** Optional timestamp of the last update (ISO 8601 format: `YYYY-MM-DDThh:mm:ss[.sss][Z or ±hh:mm]`) */ + timestamp: string; + /** Enables or disables the module. Default: `true` */ + enabled: boolean; +} + +declare module '../../src/config' { + interface Config { + shapingRules?: ShapingRulesConfig; + } +} + +const MODULE_NAME = 'shapingRules'; + +const globalRandomStore = new WeakMap<{ auctionId: string }, number>(); + +let auctionConfigStore = new Map(); + +export const dep = { + getGlobalRandom: getGlobalRandom +}; + +function getGlobalRandom(auctionId: string, auctionIndex: AuctionIndex = auctionManager.index) { + if (!auctionId) { + return Math.random(); + } + const auction = auctionIndex.getAuction({auctionId}); + if (!globalRandomStore.has(auction)) { + globalRandomStore.set(auction, Math.random()); + } + return globalRandomStore.get(auction); +} + +const unregisterFunctions: Array<() => void> = [] + +let moduleConfig: ShapingRulesConfig = { + endpoint: { + method: 'GET', + url: '' + }, + auctionDelay: 0, + extraSchemaEvaluators: {} +}; + +let fetching = false; + +let rulesLoaded = false; + +const delayedAuctions = timeoutQueue(); + +let rulesConfig: RulesConfig = null; + +export function evaluateConfig(config: RulesConfig, auctionId: string) { + if (!config || !config.ruleSets) { + logWarn(`${MODULE_NAME}: Invalid structure for rules engine`); + return; + } + + if (!config.enabled) { + logInfo(`${MODULE_NAME}: Rules engine is disabled in the configuration.`); + return; + } + + const stageRules = config.ruleSets; + + const modelGroupsWithStage = getAssignedModelGroups(stageRules || []); + + for (const { modelGroups, stage } of modelGroupsWithStage) { + const modelGroup = modelGroups.find(group => group.selected); + if (!modelGroup) continue; + evaluateRules(modelGroup.rules || [], modelGroup.schema || [], stage, modelGroup.analyticsKey, auctionId, modelGroup.default); + } +} + +export function getAssignedModelGroups(rulesets: RuleSet[]): Array<{ modelGroups: ModelGroup[], stage: string }> { + return rulesets.flatMap(ruleset => { + const { modelGroups, stage } = ruleset; + if (!modelGroups?.length) { + return []; + } + + // Calculate cumulative weights for proper weighted random selection + let cumulativeWeight = 0; + const groupsWithCumulativeWeights = modelGroups.map(group => { + const groupWeight = group.weight ?? 100; + cumulativeWeight += groupWeight; + return { + group, + cumulativeWeight + }; + }); + + const weightSum = cumulativeWeight; + // Generate random value in range [0, weightSum) + // This ensures each group gets probability proportional to its weight + const randomValue = Math.random() * weightSum; + + // Find first group where cumulative weight >= randomValue + let selectedIndex = groupsWithCumulativeWeights.findIndex(({ cumulativeWeight }) => randomValue < cumulativeWeight); + + // Fallback: if no group was selected (shouldn't happen, but safety check) + if (selectedIndex === -1) { + selectedIndex = modelGroups.length - 1; + } + + // Create new model groups array with selected flag + const newModelGroups = modelGroups.map((group, index) => ({ + ...group, + selected: index === selectedIndex + })); + + return { + modelGroups: newModelGroups, + stage + }; + }); +} + +function evaluateRules(rules, schema, stage, analyticsKey, auctionId: string, defaultResults?) { + const modelGroupConfig = auctionConfigStore.get(auctionId) || []; + modelGroupConfig.push({ + rules, + schema, + stage, + analyticsKey, + defaultResults, + }); + auctionConfigStore.set(auctionId, modelGroupConfig); +} + +const schemaEvaluators = { + percent: (args, context) => () => { + const auctionId = context.auctiondId || context.bid?.auctionId; + return dep.getGlobalRandom(auctionId) * 100 < args[0] + }, + adUnitCode: (args, context) => () => context.adUnit.code, + adUnitCodeIn: (args, context) => () => args[0].includes(context.adUnit.code), + deviceCountry: (args, context) => () => context.ortb2?.device?.geo?.country, + deviceCountryIn: (args, context) => () => args[0].includes(context.ortb2?.device?.geo?.country), + channel: (args, context) => () => 'web', + eidAvailable: (args, context) => () => { + const eids = context.ortb2?.user?.eids || []; + return eids.length > 0; + }, + userFpdAvailable: (args, context) => () => { + const fpd = context.ortb2?.user?.data || {}; + const extFpd = context.ortb2?.user?.ext?.data || {}; + const mergedFpd = { ...fpd, ...extFpd }; + return Object.keys(mergedFpd).length > 0; + }, + fpdAvailable: (args, context) => () => { + const extData = context.ortb2?.user?.ext?.data || {}; + const usrData = context.ortb2?.user?.data || {}; + const siteExtData = context.ortb2?.site?.ext?.data || {}; + const siteContentData = context.ortb2?.site?.content?.data || {}; + const appExtData = context.ortb2?.app?.ext?.data || {}; + const appContentData = context.ortb2?.app?.content?.data || {}; + const mergedFpd = { ...extData, ...usrData, ...siteExtData, ...siteContentData, ...appExtData, ...appContentData }; + return Object.keys(mergedFpd).length > 0; + }, + gppSidIn: (args, context) => () => { + const gppSids = context.ortb2?.regs?.gpp_sid || []; + return args[0].some((sid) => gppSids.includes(sid)); + }, + tcfInScope: (args, context) => () => context.ortb2?.regs?.ext?.gdpr === 1, + domain: (args, context) => () => { + const domain = context.ortb2?.site?.domain || context.ortb2?.app?.domain || ''; + return domain; + }, + domainIn: (args, context) => () => { + const domain = context.ortb2?.site?.domain || context.ortb2?.app?.domain || ''; + return args[0].includes(domain); + }, + bundle: (args, context) => () => { + const bundle = context.ortb2?.app?.bundle || ''; + return bundle; + }, + bundleIn: (args, context) => () => { + const bundle = context.ortb2?.app?.bundle || ''; + return args[0].includes(bundle); + }, + mediaTypeIn: (args, context) => () => { + const mediaTypes = Object.keys(context.adUnit?.mediaTypes) || []; + return args[0].some((type) => mediaTypes.includes(type)); + }, + deviceTypeIn: (args, context) => () => { + const deviceType = context.ortb2?.device?.devicetype; + return args[0].includes(deviceType); + }, + bidPrice: (args, context) => () => { + const [operator, currency, value] = args || []; + const {cpm: bidPrice, currency: bidCurrency} = context.bid || {}; + if (bidCurrency !== currency) { + return false; + } + if (operator === 'gt') { + return bidPrice > value; + } else if (operator === 'gte') { + return bidPrice >= value; + } else if (operator === 'lt') { + return bidPrice < value; + } else if (operator === 'lte') { + return bidPrice <= value; + } + return false; + } +}; + +export function evaluateSchema(func, args, context) { + const extraEvaluators = moduleConfig.extraSchemaEvaluators || {}; + const evaluators = { ...schemaEvaluators, ...extraEvaluators }; + const evaluator = evaluators[func]; + if (evaluator) { + return evaluator(args, context); + } + return () => null; +} + +function evaluateCondition(condition, func) { + switch (condition) { + case '*': + return true + case 'true': + return func() === true; + case 'false': + return func() === false; + default: + return func() === condition; + } +} + +export function fetchRules(endpoint = moduleConfig.endpoint) { + if (fetching) { + logWarn(`${MODULE_NAME}: A fetch is already occurring. Skipping.`); + return; + } + + if (!endpoint?.url || endpoint?.method !== 'GET') return; + + fetching = true; + ajax(endpoint.url, { + success: (response: any) => { + fetching = false; + rulesLoaded = true; + rulesConfig = JSON.parse(response); + delayedAuctions.resume(); + logInfo(`${MODULE_NAME}: Rules configuration fetched successfully.`); + }, + error: () => { + fetching = false; + } + }, null, { method: 'GET' }); +} + +export function registerActivities() { + const stages = { + [ACTIVITY_FETCH_BIDS]: 'processed-auction-request', + [ACTIVITY_ADD_BID_RESPONSE]: 'processed-auction', + }; + + [ACTIVITY_FETCH_BIDS, ACTIVITY_ADD_BID_RESPONSE].forEach(activity => { + unregisterFunctions.push( + registerActivityControl(activity, MODULE_NAME, (params) => { + const auctionId = params.auctionId || params.bid?.auctionId; + if (params[ACTIVITY_PARAM_COMPONENT_TYPE] !== MODULE_TYPE_BIDDER) return; + if (!auctionId) return; + + const checkConditions = ({schema, conditions, stage}) => { + for (const [index, schemaEntry] of schema.entries()) { + const schemaFunction = evaluateSchema(schemaEntry.function, schemaEntry.args || [], params); + if (evaluateCondition(conditions[index], schemaFunction)) { + return true; + } + } + return false; + } + + const results = []; + let modelGroups = auctionConfigStore.get(auctionId) || []; + modelGroups = modelGroups.filter(modelGroup => modelGroup.stage === stages[activity]); + + // evaluate applicable results for each model group + for (const modelGroup of modelGroups) { + // find first rule that matches conditions + const selectedRule = modelGroup.rules.find(rule => checkConditions({...rule, schema: modelGroup.schema})); + if (selectedRule) { + results.push(...selectedRule.results); + } else if (Array.isArray(modelGroup.defaultResults)) { + const defaults = modelGroup.defaultResults.map(result => ({...result, analyticsKey: modelGroup.analyticsKey})); + results.push(...defaults); + } + } + + // set analytics labels for logAtag results + results + .filter(result => result.function === 'logAtag') + .forEach((result) => { + setLabels({ [auctionId + '-' + result.analyticsKey]: result.args.analyticsValue }); + }); + + // verify current bidder against applicable rules + const allow = results + .filter(result => ['excludeBidders', 'includeBidders'].includes(result.function)) + .every((result) => { + return result.args.every(({bidders}) => { + const bidderIncluded = bidders.includes(params[ACTIVITY_PARAM_COMPONENT_NAME]); + return result.function === 'excludeBidders' ? !bidderIncluded : bidderIncluded; + }); + }); + + if (!allow) { + return { allow, reason: `Bidder ${params.bid?.bidder} excluded by rules module` }; + } + }) + ); + }); +} + +export const startAuctionHook = timedAuctionHook('rules', function startAuctionHook(fn, req) { + req.auctionId = req.auctionId || generateUUID(); + evaluateConfig(rulesConfig, req.auctionId); + fn.call(this, req); +}); + +export const requestBidsHook = timedAuctionHook('rules', function requestBidsHook(fn, reqBidsConfigObj) { + const { auctionDelay = 0 } = moduleConfig; + const continueAuction = ((that) => () => fn.call(that, reqBidsConfigObj))(this); + + if (!rulesLoaded && auctionDelay > 0) { + delayedAuctions.submit(auctionDelay, continueAuction, () => { + logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction ${reqBidsConfigObj.auctionId}`) + continueAuction(); + }); + } else { + continueAuction(); + } +}); + +function init(config: ShapingRulesConfig) { + moduleConfig = config; + registerActivities(); + auctionManager.onExpiry(auction => { + auctionConfigStore.delete(auction.getAuctionId()); + }); + // use static config if provided + if (config.rules) { + rulesConfig = config.rules; + } else { + fetchRules(); + } + getHook('requestBids').before(requestBidsHook, 50); + getHook('startAuction').before(startAuctionHook, 50); +} + +export function reset() { + try { + getHook('requestBids').getHooks({hook: requestBidsHook}).remove(); + getHook('startAuction').getHooks({hook: startAuctionHook}).remove(); + unregisterFunctions.forEach(unregister => unregister()); + unregisterFunctions.length = 0; + auctionConfigStore.clear(); + } catch (e) { + } + setLabels({}); +} + +config.getConfig(MODULE_NAME, config => init(config[MODULE_NAME])); diff --git a/src/activities/activities.js b/src/activities/activities.js index 53d73e26c3b..f436222603b 100644 --- a/src/activities/activities.js +++ b/src/activities/activities.js @@ -60,3 +60,8 @@ export const LOAD_EXTERNAL_SCRIPT = 'loadExternalScript'; * accessRequestCredentials: setting withCredentials flag in ajax request config */ export const ACTIVITY_ACCESS_REQUEST_CREDENTIALS = 'accessRequestCredentials'; + +/** + * acceptBid: a bid is about to be accepted. + */ +export const ACTIVITY_ADD_BID_RESPONSE = 'acceptBid'; diff --git a/src/adapterManager.ts b/src/adapterManager.ts index 1483899029f..7c0faf40d1d 100644 --- a/src/adapterManager.ts +++ b/src/adapterManager.ts @@ -93,7 +93,7 @@ config.getConfig('s2sConfig', config => { } }); -const activityParams = activityParamsBuilder((alias) => adapterManager.resolveAlias(alias)); +export const activityParams = activityParamsBuilder((alias) => adapterManager.resolveAlias(alias)); function getConfigName(s2sConfig) { // According to our docs, "module" bid (stored impressions) @@ -506,6 +506,45 @@ const adapterManager = { .filter(uniques) .forEach(incrementAuctionsCounter); + const ortb2 = ortb2Fragments.global || {}; + const bidderOrtb2 = ortb2Fragments.bidder || {}; + + const getTid = tidFactory(); + + const getCacheKey = (bidderCode: BidderCode, s2sActivityParams?): string => { + const s2sName = s2sActivityParams != null ? s2sActivityParams[ACTIVITY_PARAM_S2S_NAME] : ''; + return s2sName ? `${bidderCode}:${s2sName}` : `${bidderCode}:`; + }; + + const mergeBidderFpd = (() => { + const fpdCache: any = {}; + return function(auctionId: string, bidderCode: BidderCode, s2sActivityParams?) { + const cacheKey = getCacheKey(bidderCode, s2sActivityParams); + const redact = dep.redact( + s2sActivityParams != null + ? s2sActivityParams + : activityParams(MODULE_TYPE_BIDDER, bidderCode) + ); + if (fpdCache[cacheKey] !== undefined) { + return [fpdCache[cacheKey], redact]; + } + const [tid, tidSource] = getTid(bidderCode, auctionId, bidderOrtb2[bidderCode]?.source?.tid ?? ortb2.source?.tid); + const fpd = Object.freeze(redact.ortb2(mergeDeep( + {}, + ortb2, + bidderOrtb2[bidderCode], + { + source: { + tid, + ext: {tidSource} + } + } + ))); + fpdCache[cacheKey] = fpd; + return [fpd, redact]; + } + })(); + let {[PARTITIONS.CLIENT]: clientBidders, [PARTITIONS.SERVER]: serverBidders} = partitionBidders(adUnits, _s2sConfigs); const allowedBidders = new Set(); @@ -513,10 +552,22 @@ const adapterManager = { if (!isPlainObject(au.mediaTypes)) { au.mediaTypes = {}; } + // filter out bidders that cannot participate in the auction - au.bids = au.bids.filter((bid) => !bid.bidder || dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_BIDDER, bid.bidder, { - isS2S: serverBidders.includes(bid.bidder) && !clientBidders.includes(bid.bidder) - }))) + au.bids = au.bids.filter((bid) => { + if (!bid.bidder) { + return true; + } + const [ortb2] = mergeBidderFpd(auctionId, bid.bidder); + const isS2S = serverBidders.includes(bid.bidder) && !clientBidders.includes(bid.bidder); + return dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_BIDDER, bid.bidder, { + bid, + ortb2, + adUnit: au, + auctionId, + isS2S + })); + }); au.bids.forEach(bid => { allowedBidders.add(bid.bidder); }); @@ -535,29 +586,8 @@ const adapterManager = { const bidRequests: BidderRequest[] = []; - const ortb2 = ortb2Fragments.global || {}; - const bidderOrtb2 = ortb2Fragments.bidder || {}; - - const getTid = tidFactory(); - function addOrtb2>(bidderRequest: Partial, s2sActivityParams?): T { - const redact = dep.redact( - s2sActivityParams != null - ? s2sActivityParams - : activityParams(MODULE_TYPE_BIDDER, bidderRequest.bidderCode) - ); - const [tid, tidSource] = getTid(bidderRequest.bidderCode, bidderRequest.auctionId, bidderOrtb2[bidderRequest.bidderCode]?.source?.tid ?? ortb2.source?.tid); - const fpd = Object.freeze(redact.ortb2(mergeDeep( - {}, - ortb2, - bidderOrtb2[bidderRequest.bidderCode], - { - source: { - tid, - ext: {tidSource} - } - } - ))); + const [fpd, redact] = mergeBidderFpd(bidderRequest.auctionId, bidderRequest.bidderCode, s2sActivityParams); bidderRequest.ortb2 = fpd; bidderRequest.bids = bidderRequest.bids.map((bid) => { bid.ortb2 = fpd; diff --git a/src/auction.ts b/src/auction.ts index 97984dae443..a6912be69be 100644 --- a/src/auction.ts +++ b/src/auction.ts @@ -22,7 +22,7 @@ import {AUDIO, VIDEO} from './mediaTypes.js'; import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; -import adapterManager, {type BidderRequest, type BidRequest} from './adapterManager.js'; +import adapterManager, {activityParams, type BidderRequest, type BidRequest} from './adapterManager.js'; import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; import {defer, PbPromise} from './utils/promise.js'; import {type Metrics, useMetrics} from './utils/perfMetrics.js'; @@ -36,6 +36,9 @@ import type {TargetingMap} from "./targeting.ts"; import type {AdUnit} from "./adUnits.ts"; import type {MediaType} from "./mediaTypes.ts"; import type {VideoContext} from "./video.ts"; +import { isActivityAllowed } from './activities/rules.js'; +import { ACTIVITY_ADD_BID_RESPONSE } from './activities/activities.js'; +import { MODULE_TYPE_BIDDER } from './activities/modules.ts'; const { syncUsers } = userSync; @@ -252,7 +255,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a done.resolve(); events.emit(EVENTS.AUCTION_END, getProperties()); - bidsBackCallback(_adUnits, function () { + bidsBackCallback(_adUnits, auctionId, function () { try { if (_callback != null) { const bids = _bidsReceived.toArray() @@ -469,7 +472,13 @@ declare module './hook' { */ export const addBidResponse = ignoreCallbackArg(hook('async', function(adUnitCode: string, bid: Partial, reject: (reason: (typeof REJECTION_REASON)[keyof typeof REJECTION_REASON]) => void): void { if (!isValidPrice(bid)) { - reject(REJECTION_REASON.PRICE_TOO_HIGH) + reject(REJECTION_REASON.PRICE_TOO_HIGH); + } else if (!isActivityAllowed(ACTIVITY_ADD_BID_RESPONSE, activityParams(MODULE_TYPE_BIDDER, bid.bidder || bid.bidderCode, { + bid, + ortb2: auctionManager.index.getOrtb2(bid), + adUnit: auctionManager.index.getAdUnit(bid), + }))) { + reject(REJECTION_REASON.BIDDER_DISALLOWED); } else { this.dispatch.call(null, adUnitCode, bid); } @@ -487,7 +496,7 @@ export const addBidderRequests = hook('sync', function(bidderRequests) { this.dispatch.call(this.context, bidderRequests); }, 'addBidderRequests'); -export const bidsBackCallback = hook('async', function (adUnits, callback) { +export const bidsBackCallback = hook('async', function (adUnits, auctionId, callback) { if (callback) { callback(); } diff --git a/src/auctionIndex.js b/src/auctionIndex.js index 0bf0fb88943..320e247de87 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -11,6 +11,7 @@ * Bid responses are not guaranteed to have a corresponding request. * @property {function({ requestId?: string }): *} getBidRequest Returns bidRequest object for requestId. * Bid responses are not guaranteed to have a corresponding request. + * @property {function} getOrtb2 Returns ortb2 object for bid */ /** diff --git a/test/spec/modules/rules_spec.js b/test/spec/modules/rules_spec.js new file mode 100644 index 00000000000..12353de1a2e --- /dev/null +++ b/test/spec/modules/rules_spec.js @@ -0,0 +1,856 @@ +import { expect } from 'chai'; +import * as rulesModule from 'modules/rules/index.ts'; +import * as utils from 'src/utils.js'; +import * as storageManager from 'src/storageManager.js'; +import * as analyticsAdapter from 'libraries/analyticsAdapter/AnalyticsAdapter.ts'; +import { isActivityAllowed } from 'src/activities/rules.js'; +import { activityParams } from 'src/activities/activityParams.js'; +import { ACTIVITY_FETCH_BIDS, ACTIVITY_ADD_BID_RESPONSE } from 'src/activities/activities.js'; +import { MODULE_TYPE_BIDDER } from 'src/activities/modules.ts'; +import { config } from 'src/config.js'; + +describe('Rules Module', function() { + let sandbox; + let logWarnStub; + let logInfoStub; + let newStorageManagerStub; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + + logWarnStub = sandbox.stub(utils, 'logWarn'); + logInfoStub = sandbox.stub(utils, 'logInfo'); + + const mockStorageManager = { + localStorageIsEnabled: sandbox.stub().returns(true), + getDataFromLocalStorage: sandbox.stub().returns(null), + setDataInLocalStorage: sandbox.stub() + }; + newStorageManagerStub = sandbox.stub(storageManager, 'newStorageManager').returns(mockStorageManager); + }); + + afterEach(function() { + sandbox.restore(); + config.resetConfig(); + rulesModule.reset(); + }); + + describe('getAssignedModelGroups', function() { + it('should select model group based on weights', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 50, + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 50, + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }] + }]; + + // Mock Math.random to return 0.15 (15 < 50, so first group should be selected) + // randomValue = 0.15 * 100 = 15, 15 < 50 so first group selected + sandbox.stub(Math, 'random').returns(0.15); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + // Verify that first model group was selected in the returned result + expect(result[0].modelGroups[0].selected).to.be.true; + expect(result[0].modelGroups[1].selected).to.be.false; + // Verify original was not mutated + expect(rulesets[0].modelGroups[0].selected).to.be.false; + expect(rulesets[0].modelGroups[1].selected).to.be.false; + }); + + it('should use default weight of 100 when weight is not specified', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + // weight not specified, should default to 100 + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + // weight not specified, should default to 100 + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + // weight not specified, should default to 100 + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // Mock Math.random to return value that selects last group + // randomValue = 0.85 * 300 = 255 + // Cumulative weights: [100, 200, 300] + // First group: 255 < 100? No + // Second group: 255 < 200? No + // Third group: 255 < 300? Yes, selected! + sandbox.stub(Math, 'random').returns(0.85); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.false; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.true; + // Verify original was not mutated + expect(rulesets[0].modelGroups[0].selected).to.be.false; + expect(rulesets[0].modelGroups[1].selected).to.be.false; + expect(rulesets[0].modelGroups[2].selected).to.be.false; + }); + + it('should select correctly regardless of weight order (descending)', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 100, // largest first + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 50, // medium + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 25, // smallest last + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.3 * 175 = 52.5 + // Cumulative weights: [100, 150, 175] + // First group: 52.5 < 100? Yes, selected! + sandbox.stub(Math, 'random').returns(0.3); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.true; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.false; + }); + + it('should select correctly regardless of weight order (mixed)', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 30, // medium + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 100, // largest in middle + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 20, // smallest last + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.6 * 150 = 90 + // Cumulative weights: [30, 130, 150] + // First group: 90 < 30? No + // Second group: 90 < 130? Yes, selected! + sandbox.stub(Math, 'random').returns(0.6); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.false; + expect(result[0].modelGroups[1].selected).to.be.true; + expect(result[0].modelGroups[2].selected).to.be.false; + }); + + it('should select last group when randomValue is in last range', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 10, + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 20, + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 70, // largest weight + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.8 * 100 = 80 + // Cumulative weights: [10, 30, 100] + // First group: 80 < 10? No + // Second group: 80 < 30? No + // Third group: 80 < 100? Yes, selected! + sandbox.stub(Math, 'random').returns(0.8); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.false; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.true; + }); + + it('should select first group when randomValue is very small', function() { + const rulesets = [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + modelGroups: [{ + weight: 10, // smallest + selected: false, + analyticsKey: 'testKey1', + schema: [], + rules: [] + }, { + weight: 50, + selected: false, + analyticsKey: 'testKey2', + schema: [], + rules: [] + }, { + weight: 40, + selected: false, + analyticsKey: 'testKey3', + schema: [], + rules: [] + }] + }]; + + // randomValue = 0.01 * 100 = 1 + // Cumulative weights: [10, 60, 100] + // First group: 1 < 10? Yes, selected! + sandbox.stub(Math, 'random').returns(0.01); + + const result = rulesModule.getAssignedModelGroups(rulesets); + + expect(result[0].modelGroups[0].selected).to.be.true; + expect(result[0].modelGroups[1].selected).to.be.false; + expect(result[0].modelGroups[2].selected).to.be.false; + }); + }); + + describe('evaluateConfig', function() { + beforeEach(function() { + rulesModule.registerActivities(); + }); + + [ + ['processed-auction-request', ACTIVITY_FETCH_BIDS], + ['processed-auction', ACTIVITY_ADD_BID_RESPONSE] + ].forEach(([stage, activity]) => { + it(`should exclude bidder when it matches bidders list for ${stage} stage`, function() { + const rulesJson = { + enabled: true, + timestamp: '1234567890', + ruleSets: [{ + name: 'testRuleSet', + stage: stage, + version: '1.0', + modelGroups: [{ + weight: 100, + selected: true, + analyticsKey: 'testAnalyticsKey', + schema: [{ function: 'adUnitCode', args: [] }], + rules: [{ + conditions: ['adUnit-0000'], + results: [{ + function: 'excludeBidders', + args: [{ + bidders: ['bidder1'], + analyticsValue: 'excluded' + }] + }] + }] + }] + }] + }; + + sandbox.stub(Math, 'random').returns(0.5); + + const bidder1Params = activityParams(MODULE_TYPE_BIDDER, 'bidder1', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + const bidder2Params = activityParams(MODULE_TYPE_BIDDER, 'bidder2', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.true; + expect(isActivityAllowed(activity, bidder2Params)).to.be.true; + + rulesModule.evaluateConfig(rulesJson, 'test-auction-id'); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.false; + expect(isActivityAllowed(activity, bidder2Params)).to.be.true; + }); + + it(`should include only bidder when it matches bidders list for ${stage} stage`, function() { + const rulesJson = { + enabled: true, + timestamp: '1234567890', + ruleSets: [{ + name: 'testRuleSet', + stage: stage, + version: '1.0', + modelGroups: [{ + weight: 100, + selected: true, + analyticsKey: 'testAnalyticsKey', + schema: [{ function: 'adUnitCode', args: [] }], + rules: [{ + conditions: ['adUnit-0000'], + results: [{ + function: 'includeBidders', + args: [{ + bidders: ['bidder1'], + analyticsValue: 'included' + }] + }] + }] + }] + }] + }; + + sandbox.stub(Math, 'random').returns(0.5); + + const bidder1Params = activityParams(MODULE_TYPE_BIDDER, 'bidder1', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + const bidder2Params = activityParams(MODULE_TYPE_BIDDER, 'bidder2', { + adUnit: { code: 'adUnit-0000' }, + auctionId: 'test-auction-id' + }); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.true; + expect(isActivityAllowed(activity, bidder2Params)).to.be.true; + + rulesModule.evaluateConfig(rulesJson, 'test-auction-id'); + + expect(isActivityAllowed(activity, bidder1Params)).to.be.true; + expect(isActivityAllowed(activity, bidder2Params)).to.be.false; + }); + }); + + it('should execute default rules when provided and no rules match', function() { + const setLabelsStub = sandbox.stub(analyticsAdapter, 'setLabels'); + const rulesJson = { + enabled: true, + timestamp: '1234567890', + ruleSets: [{ + name: 'testRuleSet', + stage: 'processed-auction-request', + version: '1.0', + modelGroups: [{ + weight: 100, + selected: true, + analyticsKey: 'testAnalyticsKey', + schema: [{ + function: 'percent', + args: [5] + }], + default: [{ + function: 'logAtag', + args: { analyticsValue: 'default-allow' } + }], + rules: [{ + conditions: ['true'], + results: [{ + function: 'excludeBidders', + args: [{ + bidders: ['bidder1'], + analyticsValue: 'excluded' + }] + }] + }] + }] + }] + }; + + sandbox.stub(Math, 'random').returns(0.5); + const auctionId = 'test-auction-id'; + rulesModule.evaluateConfig(rulesJson, auctionId); + + const bidder1Params = activityParams(MODULE_TYPE_BIDDER, 'bidder1', { + auctionId + }); + + expect(isActivityAllowed(ACTIVITY_FETCH_BIDS, bidder1Params)).to.be.true; + + expect(setLabelsStub.calledWith({ [auctionId + '-testAnalyticsKey']: 'default-allow' })).to.be.true; + + setLabelsStub.resetHistory(); + }); + }); + + describe('getGlobalRandom', function() { + it('should return the same value for the same auctionId and call Math.random only once', function() { + const auctionId = 'test-auction-id'; + const otherAuctionId = 'other-auction-id'; + const mathRandomStub = sandbox.stub(Math, 'random').returns(0.42); + const auction1 = {auctionId: auctionId}; + const auction2 = {auctionId: otherAuctionId}; + const auctions = { + [auctionId]: auction1, + [otherAuctionId]: auction2 + } + + const index = { + getAuction: ({auctionId}) => auctions[auctionId] + } + + const result1 = rulesModule.dep.getGlobalRandom(auctionId, index); + const result2 = rulesModule.dep.getGlobalRandom(auctionId, index); + const result3 = rulesModule.dep.getGlobalRandom(auctionId, index); + + expect(result1).to.equal(0.42); + expect(result2).to.equal(0.42); + expect(result3).to.equal(0.42); + expect(mathRandomStub.calledOnce).to.equal(true); + + mathRandomStub.returns(0.99); + const result4 = rulesModule.dep.getGlobalRandom(otherAuctionId, index); + + expect(result4).to.equal(0.99); + expect(mathRandomStub.calledTwice).to.equal(true); + }); + }); + + describe('evaluateSchema', function() { + it('should evaluate percent condition', function() { + sandbox.stub(rulesModule.dep, 'getGlobalRandom').returns(0.3); + const func = rulesModule.evaluateSchema('percent', [50], {}); + const result = func(); + // 30 < 50, so should return true + expect(result).to.be.true; + }); + + it('should evaluate adUnitCode condition', function() { + const context = { + adUnit: { + code: 'div-1' + } + }; + const func = rulesModule.evaluateSchema('adUnitCode', [], context); + expect(func()).to.equal('div-1'); + + const func2 = rulesModule.evaluateSchema('adUnitCode', [], context); + expect(func2()).to.equal('div-1'); + }); + + it('should evaluate adUnitCodeIn condition', function() { + const context = { + adUnit: { + code: 'div-1' + } + }; + const func = rulesModule.evaluateSchema('adUnitCodeIn', [['div-1', 'div-2']], context); + expect(func()).to.be.true; + + const func2 = rulesModule.evaluateSchema('adUnitCodeIn', [['div-3', 'div-4']], context); + expect(func2()).to.be.false; + }); + + it('should evaluate deviceCountry condition', function() { + const context = { + ortb2: { + device: { + geo: { + country: 'US' + } + } + } + }; + const func = rulesModule.evaluateSchema('deviceCountry', [], context); + expect(func()).to.equal('US'); + + const func2 = rulesModule.evaluateSchema('deviceCountry', [], context); + expect(func2()).to.equal('US'); + }); + + it('should evaluate deviceCountryIn condition', function() { + const context = { + ortb2: { + device: { + geo: { + country: 'US' + } + } + } + }; + const func = rulesModule.evaluateSchema('deviceCountryIn', [['US', 'UK']], context); + expect(func()).to.be.true; + + const func2 = rulesModule.evaluateSchema('deviceCountryIn', [['DE', 'FR']], context); + expect(func2()).to.be.false; + }); + + it('should evaluate channel condition', function() { + const context1 = { + ortb2: { + ext: { + prebid: { + channel: 'pbjs' + } + } + } + }; + const func1 = rulesModule.evaluateSchema('channel', [], context1); + expect(func1()).to.equal('web'); + }); + + it('should evaluate eidAvailable condition', function() { + const context1 = { + ortb2: { + user: { + eids: [{ source: 'test', id: '123' }] + } + } + }; + const func1 = rulesModule.evaluateSchema('eidAvailable', [], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + user: { + eids: [] + } + } + }; + const func2 = rulesModule.evaluateSchema('eidAvailable', [], context2); + expect(func2()).to.be.false; + }); + + it('should evaluate userFpdAvailable condition', function() { + const context1 = { + ortb2: { + user: { + data: [{ name: 'test', segment: [] }] + } + } + }; + const func1 = rulesModule.evaluateSchema('userFpdAvailable', [], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + user: { + ext: { + data: [{ name: 'test', segment: [] }] + } + } + } + }; + const func2 = rulesModule.evaluateSchema('userFpdAvailable', [], context2); + expect(func2()).to.be.true; + + const context3 = { + ortb2: { + user: {} + } + }; + const func3 = rulesModule.evaluateSchema('userFpdAvailable', [], context3); + expect(func3()).to.be.false; + }); + + it('should evaluate fpdAvailable condition', function() { + const context1 = { + ortb2: { + user: { + data: [{ name: 'test' }] + } + } + }; + const func1 = rulesModule.evaluateSchema('fpdAvailable', [], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + site: { + content: { + data: [{ name: 'test' }] + } + } + } + }; + const func2 = rulesModule.evaluateSchema('fpdAvailable', [], context2); + expect(func2()).to.be.true; + + const context3 = { + ortb2: {} + }; + const func3 = rulesModule.evaluateSchema('fpdAvailable', [], context3); + expect(func3()).to.be.false; + }); + + it('should evaluate gppSidIn condition', function() { + const context1 = { + ortb2: { + regs: { + gpp_sid: [1, 2, 3] + } + } + }; + const func1 = rulesModule.evaluateSchema('gppSidIn', [[2]], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('gppSidIn', [[4]], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate tcfInScope condition', function() { + const context1 = { + ortb2: { + regs: { + ext: { + gdpr: 1 + } + } + } + }; + const func1 = rulesModule.evaluateSchema('tcfInScope', [], context1); + expect(func1()).to.be.true; + + const context2 = { + regs: { + ext: { + gdpr: 0 + } + } + }; + const func2 = rulesModule.evaluateSchema('tcfInScope', [], context2); + expect(func2()).to.be.false; + }); + + it('should evaluate domain condition', function() { + const context1 = { + ortb2: { + site: { + domain: 'example.com' + } + } + }; + const func1 = rulesModule.evaluateSchema('domain', [], context1); + expect(func1()).to.equal('example.com'); + + const context2 = { + ortb2: { + app: { + domain: 'app.example.com' + } + } + }; + const func2 = rulesModule.evaluateSchema('domain', [], context2); + expect(func2()).to.equal('app.example.com'); + + const context3 = { + ortb2: {} + }; + const func3 = rulesModule.evaluateSchema('domain', [], context3); + expect(func3()).to.equal(''); + }); + + it('should evaluate domainIn condition', function() { + const context1 = { + ortb2: { + site: { + domain: 'example.com' + } + } + }; + const func1 = rulesModule.evaluateSchema('domainIn', [['example.com', 'test.com']], context1); + expect(func1()).to.be.true; + + const context2 = { + ortb2: { + app: { + domain: 'app.example.com' + } + } + }; + const func2 = rulesModule.evaluateSchema('domainIn', [['app.example.com']], context2); + expect(func2()).to.be.true; + + const func3 = rulesModule.evaluateSchema('domainIn', [['other.com']], context1); + expect(func3()).to.be.false; + }); + + it('should evaluate bundle condition', function() { + const context1 = { + ortb2: { + app: { + bundle: 'com.example.app' + } + } + }; + const func1 = rulesModule.evaluateSchema('bundle', [], context1); + expect(func1()).to.equal('com.example.app'); + + const context2 = { + ortb2: {} + }; + const func2 = rulesModule.evaluateSchema('bundle', [], context2); + expect(func2()).to.equal(''); + }); + + it('should evaluate bundleIn condition', function() { + const context1 = { + ortb2: { + app: { + bundle: 'com.example.app' + } + } + }; + const func1 = rulesModule.evaluateSchema('bundleIn', ['com.example.app'], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('bundleIn', [['com.other.app']], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate mediaTypeIn condition', function() { + const context1 = { + adUnit: { + mediaTypes: { + banner: {}, + video: {} + } + } + }; + const func1 = rulesModule.evaluateSchema('mediaTypeIn', [['banner']], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('mediaTypeIn', [['native']], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate deviceTypeIn condition', function() { + const context1 = { + ortb2: { + device: { + devicetype: 2 + } + } + }; + const func1 = rulesModule.evaluateSchema('deviceTypeIn', [[2, 3]], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('deviceTypeIn', [[4, 5]], context1); + expect(func2()).to.be.false; + }); + + it('should evaluate bidPrice condition', function() { + const context1 = { + bid: { + cpm: 5.50, + currency: 'USD' + } + }; + const func1 = rulesModule.evaluateSchema('bidPrice', ['gt', 'USD', 5.0], context1); + expect(func1()).to.be.true; + + const func2 = rulesModule.evaluateSchema('bidPrice', ['gt', 'USD', 6.0], context1); + expect(func2()).to.be.false; + + const func3 = rulesModule.evaluateSchema('bidPrice', ['lte', 'USD', 6.0], context1); + expect(func3()).to.be.true; + + const context3 = { + bid: { + cpm: 0, + currency: 'USD' + } + }; + const func4 = rulesModule.evaluateSchema('bidPrice', ['gt', 'USD', 1.0], context3); + expect(func4()).to.be.false; + }); + + it('should return null function for unknown schema function', function() { + const func = rulesModule.evaluateSchema('unknownFunction', [], {}); + expect(func()).to.be.null; + }); + + describe('extraSchemaEvaluators', function() { + it('should use custom browser evaluator from extraSchemaEvaluators', function() { + const browserEvaluator = (args, context) => { + return () => { + const userAgent = context.ortb2?.device?.ua || navigator.userAgent; + if (userAgent.includes('Chrome')) return 'Chrome'; + if (userAgent.includes('Firefox')) return 'Firefox'; + if (userAgent.includes('Safari')) return 'Safari'; + if (userAgent.includes('Edge')) return 'Edge'; + return 'Unknown'; + }; + }; + + config.setConfig({ + shapingRules: { + extraSchemaEvaluators: { + browser: browserEvaluator + } + } + }); + + const context1 = { + ortb2: { + device: { + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0' + } + } + }; + + const func1 = rulesModule.evaluateSchema('browser', [], context1); + expect(func1()).to.equal('Chrome'); + + const context2 = { + ortb2: { + device: { + ua: 'Mozilla/5.0 Firefox/121.0.0' + } + } + }; + const func2 = rulesModule.evaluateSchema('browser', [], context2); + expect(func2()).to.equal('Firefox'); + }); + }); + }); +}); From 11c4757979a57172c7c1e61e487338ff46309d09 Mon Sep 17 00:00:00 2001 From: yndxcdn Date: Thu, 5 Feb 2026 22:53:52 +0300 Subject: [PATCH 023/105] Update yandexBidAdapter.md (#14416) - Added email address to the main description - Added 'cur' parameter to the parameters list - Minor changes in the Adunit config examples --- modules/yandexBidAdapter.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md index ac454285806..9a7684d2644 100644 --- a/modules/yandexBidAdapter.md +++ b/modules/yandexBidAdapter.md @@ -8,40 +8,42 @@ Maintainer: prebid@yandex-team.com # Description -The Yandex Prebid Adapter is designed for seamless integration with Yandex's advertising services. It facilitates effective bidding by leveraging Yandex's robust ad-serving technology, ensuring publishers can maximize their ad revenue through efficient and targeted ad placements. +The Yandex Prebid Adapter is designed for seamless integration with Yandex's advertising services. It facilitates effective bidding by leveraging Yandex's robust ad-serving technology, ensuring publishers can maximize their ad revenue through efficient and targeted ad placements. Please reach out to for the integration guide and more details. For comprehensive auction analytics, consider using the [Yandex Analytics Adapter](https://docs.prebid.org/dev-docs/analytics/yandex.html). This tool provides essential insights into auction dynamics and user interactions, empowering publishers to fine-tune their strategies for optimal ad performance. # Parameters -| Name | Required? | Description | Example | Type | -|---------------|--------------------------------------------|-------------|---------|-----------| -| `placementId` | Yes | Block ID | `123-1` | `String` | -| `pageId` | No
Deprecated. Please use `placementId` | Page ID | `123` | `Integer` | -| `impId` | No
Deprecated. Please use `placementId` | Imp ID | `1` | `Integer` | +| Name | Scope | Description | Example | Type | +|---------------|----------------------------------------|--------------|------------------|-----------| +| `placementId` | Required | Placement ID | `'R-X-123456-1'` | `String` | +| `cur` | Optional. Default value is `'EUR'` | Bid Currency | `'USD'` | `String` | +| `pageId` | `Deprecated`. Please use `placementId` | Page ID | `123` | `Integer` | +| `impId` | `Deprecated`. Please use `placementId` | Imp ID | `1` | `Integer` | # Test Parameters ```javascript var adUnits = [ - { // banner + { // banner example. please check if the 'placementId' is active in Yandex UI code: 'banner-1', mediaTypes: { banner: { - sizes: [[240, 400], [300, 600]], + sizes: [[300, 250], [300, 600]], } }, bids: [ { bidder: 'yandex', params: { - placementId: '346580-1' + placementId: 'R-A-346580-1', + cur: 'USD' }, } ], }, - { // video - code: 'banner-2', + { // video example. please check if the 'placementId' is active in Yandex UI + code: 'video-1', mediaTypes: { video: { sizes: [[640, 480]], @@ -57,13 +59,14 @@ var adUnits = [ { bidder: 'yandex', params: { - placementId: '346580-1' + placementId: 'R-V-346580-1', + cur: 'USD' }, } ], }, - { // native - code: 'banner-3',, + { // native example. please check if the 'placementId' is active in Yandex UI + code: 'native-1', mediaTypes: { native: { title: { @@ -84,7 +87,7 @@ var adUnits = [ len: 90 }, sponsoredBy: { - len: 25, + len: 25 } }, }, @@ -92,7 +95,8 @@ var adUnits = [ { bidder: 'yandex', params: { - placementId: '346580-1' + placementId: 'R-A-346580-2', + cur: 'USD' }, } ], From d467970835b163f02a7f0085bca0cdfd1bb7e249 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 5 Feb 2026 11:56:29 -0800 Subject: [PATCH 024/105] new rubicon apex url (#14417) --- modules/rubiconBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index e8166c16f59..477da80d420 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -30,7 +30,7 @@ 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.2.1.js'; +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') || {}; From cceccba68c923272e4ff3ef4de418a1a91bf836c Mon Sep 17 00:00:00 2001 From: MaksymTeqBlaze Date: Thu, 5 Feb 2026 21:57:44 +0200 Subject: [PATCH 025/105] TeqBlaze Sales Agent Bid Adapter: initial release (#14413) * TeqBlazeSalesAgent Bid Adapter: initial release * update doc --------- Co-authored-by: Patrick McCann --- modules/teqBlazeSalesAgentBidAdapter.js | 41 ++ modules/teqBlazeSalesAgentBidAdapter.md | 79 ++++ .../teqBlazeSalesAgentBidAdapter_spec.js | 440 ++++++++++++++++++ 3 files changed, 560 insertions(+) create mode 100644 modules/teqBlazeSalesAgentBidAdapter.js create mode 100644 modules/teqBlazeSalesAgentBidAdapter.md create mode 100644 test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js diff --git a/modules/teqBlazeSalesAgentBidAdapter.js b/modules/teqBlazeSalesAgentBidAdapter.js new file mode 100644 index 00000000000..f2cbf2d57db --- /dev/null +++ b/modules/teqBlazeSalesAgentBidAdapter.js @@ -0,0 +1,41 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { + buildPlacementProcessingFunction, + buildRequestsBase, + interpretResponse, + isBidRequestValid +} from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'teqBlazeSalesAgent'; +const AD_URL = 'https://be-agent.teqblaze.io/pbjs'; + +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + const aeeSignals = bidderRequest.ortb2?.site?.ext?.data?.scope3_aee; + + if (aeeSignals) { + placement.axei = aeeSignals.include; + placement.axex = aeeSignals.exclude; + + if (aeeSignals.macro) { + placement.axem = aeeSignals.macro; + } + } +}; + +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests, + interpretResponse +}; + +registerBidder(spec); diff --git a/modules/teqBlazeSalesAgentBidAdapter.md b/modules/teqBlazeSalesAgentBidAdapter.md new file mode 100644 index 00000000000..d0c1475643d --- /dev/null +++ b/modules/teqBlazeSalesAgentBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: TeqBlaze Sales Agent Bidder Adapter +Module Type: TeqBlaze Sales Agent Bidder Adapter +Maintainer: support@teqblaze.com +``` + +# Description + +Connects to TeqBlaze Sales Agent for bids. +TeqBlaze Sales Agent bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'teqBlazeSalesAgent', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'teqBlazeSalesAgent', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'teqBlazeSalesAgent', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js b/test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js new file mode 100644 index 00000000000..f2dbe70f30d --- /dev/null +++ b/test/spec/modules/teqBlazeSalesAgentBidAdapter_spec.js @@ -0,0 +1,440 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/teqBlazeSalesAgentBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'teqBlazeSalesAgent'; + +describe('TeqBlazeSalesAgentBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK', + }, + site: { + ext: { + data: { + scope3_aee: { + include: 'include', + exclude: 'exclude', + macro: 'macro' + } + } + } + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + expect(placement.axei).to.exist.and.to.be.equal('include'); + expect(placement.axex).to.exist.and.to.be.equal('exclude'); + expect(placement.axem).to.exist.and.to.be.equal('macro'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + const serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + const serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From ddf5a0f37b3e5889ad967faac9e461f38698ada0 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 6 Feb 2026 14:32:13 +0000 Subject: [PATCH 026/105] Prebid 10.24.0 release --- metadata/modules.json | 14 + metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.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 | 17 +- 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 | 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/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 | 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 | 2 +- metadata/modules/conversantBidAdapter.json | 2 +- metadata/modules/copper6sspBidAdapter.json | 2 +- metadata/modules/cpmstarBidAdapter.json | 2 +- metadata/modules/criteoBidAdapter.json | 6 +- metadata/modules/criteoIdSystem.json | 6 +- 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 +- .../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/holidBidAdapter.json | 2 +- metadata/modules/hybridBidAdapter.json | 2 +- metadata/modules/id5IdSystem.json | 246 +++++++++++++++++- 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/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 | 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/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/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/pulsepointBidAdapter.json | 2 +- metadata/modules/quantcastBidAdapter.json | 2 +- metadata/modules/quantcastIdSystem.json | 2 +- metadata/modules/r2b2BidAdapter.json | 2 +- metadata/modules/readpeakBidAdapter.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/setupadBidAdapter.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 | 24 +- 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 +- .../modules/teqBlazeSalesAgentBidAdapter.json | 13 + 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/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 | 16 +- package.json | 2 +- 276 files changed, 603 insertions(+), 305 deletions(-) create mode 100644 metadata/modules/teqBlazeSalesAgentBidAdapter.json diff --git a/metadata/modules.json b/metadata/modules.json index b078a117e6e..ff1fde0e0d3 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -519,6 +519,13 @@ "gvlid": 1283, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "intlscoop", + "aliasOf": "adkernel", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "admaru", @@ -4579,6 +4586,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "teqBlazeSalesAgent", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "theadx", diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 7c784ce23b2..77389ad4d4b 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-01-28T16:29:58.218Z", + "timestamp": "2026-02-06T14:30:25.255Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 08c9e5fdb3c..fca65dcb24a 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-01-28T16:29:58.314Z", + "timestamp": "2026-02-06T14:30:25.357Z", "disclosures": [] } }, diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index 5c1719c2ce1..f1dfa930efc 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-01-28T16:29:58.317Z", + "timestamp": "2026-02-06T14:30:25.360Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index b490d929e1a..a26ec7be9e2 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-01-28T16:29:58.354Z", + "timestamp": "2026-02-06T14:30:25.409Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index e604c3b2d9c..828963179b5 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-01-28T16:29:58.504Z", + "timestamp": "2026-02-06T14:30:25.495Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index 6b21766ca1d..72e73cf37a2 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-01-28T16:29:58.504Z", + "timestamp": "2026-02-06T14:30:25.496Z", "disclosures": [] } }, diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index 0d880b30acc..205cb9acffc 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-01-28T16:29:58.826Z", + "timestamp": "2026-02-06T14:30:25.799Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index fb6d19f8ad4..b05d8af9354 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-01-28T16:29:59.445Z", + "timestamp": "2026-02-06T14:30:26.461Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 57df8f4f68b..a7f48e14000 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-01-28T16:29:59.445Z", + "timestamp": "2026-02-06T14:30:26.461Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index 907ed2be2df..0c00d87b9dd 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-01-28T16:29:59.885Z", + "timestamp": "2026-02-06T14:30:26.813Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 88388af4def..6c4231222ae 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-01-28T16:30:00.155Z", + "timestamp": "2026-02-06T14:30:27.081Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index 29fbe50a428..108f1432234 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-01-28T16:30:00.288Z", + "timestamp": "2026-02-06T14:30:27.216Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index 3cefa08504a..e41072b3399 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-01-28T16:30:00.508Z", + "timestamp": "2026-02-06T14:30:27.264Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,19 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:00.508Z", + "timestamp": "2026-02-06T14:30:27.264Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2026-01-28T16:30:00.562Z", + "timestamp": "2026-02-06T14:30:27.312Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:01.305Z", + "timestamp": "2026-02-06T14:30:28.744Z", "disclosures": [] }, "https://appmonsta.ai/DeviceStorageDisclosure.json": { - "timestamp": "2026-01-28T16:30:01.323Z", + "timestamp": "2026-02-06T14:30:28.767Z", "disclosures": [] } }, @@ -348,6 +348,13 @@ "aliasOf": "adkernel", "gvlid": 1283, "disclosureURL": "https://appmonsta.ai/DeviceStorageDisclosure.json" + }, + { + "componentType": "bidder", + "componentName": "intlscoop", + "aliasOf": "adkernel", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index f44a99df523..a5648b2fd80 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-01-28T16:30:03.161Z", + "timestamp": "2026-02-06T14:30:30.459Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:02.786Z", + "timestamp": "2026-02-06T14:30:30.104Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index f4aceb4271e..5b0e9036f72 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-01-28T16:30:03.161Z", + "timestamp": "2026-02-06T14:30:30.460Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index 9e56d98f973..17577746d97 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-01-28T16:30:03.552Z", + "timestamp": "2026-02-06T14:30:30.875Z", "disclosures": [] } }, diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index 85b183c233f..bcbf1f51826 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-01-28T16:30:03.552Z", + "timestamp": "2026-02-06T14:30:30.875Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index faa59453320..8c342378088 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-01-28T16:30:03.784Z", + "timestamp": "2026-02-06T14:30:31.171Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index 88033c0bd3a..bec37b2eccf 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-01-28T16:30:04.118Z", + "timestamp": "2026-02-06T14:30:31.506Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index 434185cfb11..35491e4fee8 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-01-28T16:30:04.118Z", + "timestamp": "2026-02-06T14:30:31.506Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index 7e1e6a0aff6..19e0d9f1889 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-01-28T16:30:04.676Z", + "timestamp": "2026-02-06T14:30:32.064Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index ffa30b608f0..40aef697dc1 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-01-28T16:30:04.714Z", + "timestamp": "2026-02-06T14:30:32.188Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index 9525503e59b..910fa94d220 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-01-28T16:30:04.745Z", + "timestamp": "2026-02-06T14:30:32.219Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index cc0f9edaf73..b86e9acf95f 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-01-28T16:30:05.099Z", + "timestamp": "2026-02-06T14:30:32.589Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 1b11ddd4322..3a79c37e751 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-01-28T16:30:05.100Z", + "timestamp": "2026-02-06T14:30:32.589Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index 186d5188d3b..a084a15f496 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-01-28T16:30:05.144Z", + "timestamp": "2026-02-06T14:30:32.668Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index 5ca832a5cfb..c2b0952970f 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-01-28T16:30:05.446Z", + "timestamp": "2026-02-06T14:30:32.951Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index 5df3a013203..50146935690 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-01-28T16:30:05.446Z", + "timestamp": "2026-02-06T14:30:32.951Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { - "timestamp": "2026-01-28T16:30:05.465Z", + "timestamp": "2026-02-06T14:30:32.971Z", "disclosures": [ { "identifier": "waterFallCacheAnsKey_*", @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-01-28T16:30:05.651Z", + "timestamp": "2026-02-06T14:30:33.182Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index 4793bd2971a..94b04c7d058 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-01-28T16:30:05.794Z", + "timestamp": "2026-02-06T14:30:33.246Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index 763a2faa1b7..a56583d8c4f 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-01-28T16:30:05.795Z", + "timestamp": "2026-02-06T14:30:33.248Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index ad36769e5f0..b90843b0a7a 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-01-28T16:30:05.815Z", + "timestamp": "2026-02-06T14:30:33.268Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index d490880b416..05330eeef58 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-01-28T16:30:06.273Z", + "timestamp": "2026-02-06T14:30:33.690Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index f80e84a7d63..c7f6757a550 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-01-28T16:30:06.319Z", + "timestamp": "2026-02-06T14:30:33.737Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json index 0bb0131e96f..ff0cbd6ba7f 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-01-28T16:30:06.612Z", + "timestamp": "2026-02-06T14:30:34.018Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index 8de34154ecc..e9df2504210 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-01-28T16:30:07.054Z", + "timestamp": "2026-02-06T14:30:34.487Z", "disclosures": [] } }, diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index 1a8270a07dd..bdd6de82d13 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-01-28T16:30:07.197Z", + "timestamp": "2026-02-06T14:30:34.549Z", "disclosures": [] } }, diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index e2d0cb92554..a11d3a76afc 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-01-28T16:30:07.197Z", + "timestamp": "2026-02-06T14:30:34.549Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 3da9ac7b85c..3aa9241731f 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://static.anonymised.io/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:07.304Z", + "timestamp": "2026-02-06T14:30:34.706Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index 8bd90c00e58..a8f2b6c744f 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-01-28T16:30:07.420Z", + "timestamp": "2026-02-06T14:30:34.820Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index 2f0f4655a71..4c2ab0d1740 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-01-28T16:30:07.455Z", + "timestamp": "2026-02-06T14:30:34.860Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index ca815263571..2c970b21751 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-01-28T16:30:08.134Z", + "timestamp": "2026-02-06T14:30:35.499Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:07.591Z", + "timestamp": "2026-02-06T14:30:35.031Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2026-01-28T16:30:07.694Z", + "timestamp": "2026-02-06T14:30:35.046Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2026-01-28T16:30:08.134Z", + "timestamp": "2026-02-06T14:30:35.499Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index b74c3d9b2e8..8bf8b136a69 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-01-28T16:30:08.156Z", + "timestamp": "2026-02-06T14:30:35.522Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json index 3ddca493ec1..b9ba745c2f7 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-01-28T16:30:08.218Z", + "timestamp": "2026-02-06T14:30:35.588Z", "disclosures": [ { "identifier": "vendor-id", diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index b45081cbc64..eeb6a9ec330 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-01-28T16:30:08.230Z", + "timestamp": "2026-02-06T14:30:35.605Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index 561717d5ac0..c0a2ffa3a37 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-01-28T16:30:08.251Z", + "timestamp": "2026-02-06T14:30:35.629Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index 97d8889684e..66a7077498f 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-01-28T16:30:08.288Z", + "timestamp": "2026-02-06T14:30:35.669Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 138bb4cf730..90a4646478b 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-01-28T16:30:08.325Z", + "timestamp": "2026-02-06T14:30:35.711Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index d9ea10bb0eb..ccda8e82a25 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-01-28T16:30:08.359Z", + "timestamp": "2026-02-06T14:30:35.734Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 7de553c3356..3c641594e79 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-01-28T16:30:08.381Z", + "timestamp": "2026-02-06T14:30:35.859Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index cdeb282e76d..595bf81ed4d 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-01-28T16:30:08.501Z", + "timestamp": "2026-02-06T14:30:35.979Z", "disclosures": [] } }, diff --git a/metadata/modules/bidfuseBidAdapter.json b/metadata/modules/bidfuseBidAdapter.json index e9f10a2af75..d701ccceafa 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-01-28T16:30:08.568Z", + "timestamp": "2026-02-06T14:30:36.046Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index c698dd1c7e1..09ff4540889 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-01-28T16:30:09.193Z", + "timestamp": "2026-02-06T14:30:36.228Z", "disclosures": [] } }, diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index f8747a74d23..b5319641652 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-01-28T16:30:09.243Z", + "timestamp": "2026-02-06T14:30:36.303Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index 2daef968201..25859afef10 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-01-28T16:30:09.554Z", + "timestamp": "2026-02-06T14:30:36.617Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index b6e60f793e3..374fcc11b25 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-01-28T16:30:09.898Z", + "timestamp": "2026-02-06T14:30:36.985Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index d5459ddd030..2b35370569f 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-01-28T16:30:10.177Z", + "timestamp": "2026-02-06T14:30:37.211Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index a7f11357ec6..758a3c07571 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-01-28T16:30:10.533Z", + "timestamp": "2026-02-06T14:30:37.561Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index 2e6fbbf4f05..48851fd7626 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-01-28T16:30:10.546Z", + "timestamp": "2026-02-06T14:30:37.589Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 3141704c3cf..835e279b42b 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-01-28T16:30:10.571Z", + "timestamp": "2026-02-06T14:30:37.609Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index 4983eb994cd..af4800d8534 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-01-28T16:30:10.712Z", + "timestamp": "2026-02-06T14:30:37.760Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index 5756973a60a..a1e0b424f61 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-01-28T16:30:10.730Z", + "timestamp": "2026-02-06T14:30:37.778Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index bf06a9d157f..de57e158f65 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-01-28T16:30:10.790Z", + "timestamp": "2026-02-06T14:30:37.889Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index 62d8ea2cf73..d1aa7a19217 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-01-28T16:29:58.215Z", + "timestamp": "2026-02-06T14:30:25.253Z", "disclosures": [ { "identifier": "iabToFwMappingkey", diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 9d6ee591a78..61552d22ccc 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-01-28T16:30:11.083Z", + "timestamp": "2026-02-06T14:30:38.204Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index f01c1535e6c..4e49e71299f 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-01-28T16:30:11.410Z", + "timestamp": "2026-02-06T14:30:38.534Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index 098d2ef463b..b48b489d86a 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-01-28T16:30:11.411Z", + "timestamp": "2026-02-06T14:30:38.535Z", "disclosures": [] } }, diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index 0e34d3b794e..b40fec31eb5 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-01-28T16:30:11.830Z", + "timestamp": "2026-02-06T14:30:38.977Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index a8dc49de2f9..e2b3453fc1e 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-01-28T16:30:11.853Z", + "timestamp": "2026-02-06T14:30:38.991Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index ceff6354d6a..534cbef21d7 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-01-28T16:30:11.876Z", + "timestamp": "2026-02-06T14:30:39.010Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index fc825b87474..9b484e3023e 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-01-28T16:30:11.964Z", + "timestamp": "2026-02-06T14:30:39.089Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index 449b80e8442..5e32a0aa2a3 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-01-28T16:30:11.985Z", + "timestamp": "2026-02-06T14:30:39.112Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index 2fe9de26b24..108a10a9cad 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-01-28T16:30:12.429Z", + "timestamp": "2026-02-06T14:30:39.547Z", "disclosures": null } }, diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index 61dd65570fe..d6cc6a12102 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-01-28T16:30:12.505Z", + "timestamp": "2026-02-06T14:30:39.576Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 962b5946cd8..e67194a602b 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-01-28T16:30:12.542Z", + "timestamp": "2026-02-06T14:30:39.589Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index 253fd1b3b2d..db5868ef61c 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-01-28T16:30:12.576Z", + "timestamp": "2026-02-06T14:30:39.640Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index 8d62de75bc0..bfafd8a0d65 100644 --- a/metadata/modules/criteoBidAdapter.json +++ b/metadata/modules/criteoBidAdapter.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://privacy.criteo.com/iab-europe/tcfv2/disclosure": { - "timestamp": "2026-01-28T16:30:12.617Z", + "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { + "timestamp": "2026-02-06T14:30:39.749Z", "disclosures": [ { "identifier": "criteo_fast_bid", @@ -69,7 +69,7 @@ "componentName": "criteo", "aliasOf": null, "gvlid": 91, - "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure" + "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json" } ] } \ No newline at end of file diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index 6a64ec8205c..9d66fcadc10 100644 --- a/metadata/modules/criteoIdSystem.json +++ b/metadata/modules/criteoIdSystem.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://privacy.criteo.com/iab-europe/tcfv2/disclosure": { - "timestamp": "2026-01-28T16:30:12.634Z", + "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { + "timestamp": "2026-02-06T14:30:39.764Z", "disclosures": [ { "identifier": "criteo_fast_bid", @@ -68,7 +68,7 @@ "componentType": "userId", "componentName": "criteo", "gvlid": 91, - "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure", + "disclosureURL": "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json", "aliasOf": null } ] diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index c7a9dc12ec0..45692087635 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-01-28T16:30:12.634Z", + "timestamp": "2026-02-06T14:30:39.764Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index b6bd886fdd5..8269140ee19 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-01-28T16:30:12.660Z", + "timestamp": "2026-02-06T14:30:39.788Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index bccf9e38e58..73d7ca02758 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-01-28T16:30:13.069Z", + "timestamp": "2026-02-06T14:30:40.388Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index 63533ff21b1..ca52e87af81 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-01-28T16:29:58.214Z", + "timestamp": "2026-02-06T14:30:25.252Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index 0328b249ca0..4e2f9815163 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-01-28T16:30:13.171Z", + "timestamp": "2026-02-06T14:30:40.493Z", "disclosures": [] } }, diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index 4bdab1c5991..ea24b5e1c75 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-01-28T16:30:13.310Z", + "timestamp": "2026-02-06T14:30:40.626Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index 421bf1dc518..c4b33474e5f 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-01-28T16:30:13.748Z", + "timestamp": "2026-02-06T14:30:41.054Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index 8a1c226d253..b9cb8b8e666 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-01-28T16:30:14.164Z", + "timestamp": "2026-02-06T14:30:41.134Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 3f71ed4c5e4..88c2a5a969e 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-01-28T16:30:14.165Z", + "timestamp": "2026-02-06T14:30:41.134Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index 7bc7a6fbaa2..df1f7b129c1 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-01-28T16:30:14.541Z", + "timestamp": "2026-02-06T14:30:41.485Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index 2ef6917eac2..e4ad3341877 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-01-28T16:30:14.843Z", + "timestamp": "2026-02-06T14:30:41.515Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index f24747edfe1..8d91890834c 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-01-28T16:30:15.632Z", + "timestamp": "2026-02-06T14:30:42.269Z", "disclosures": [] } }, diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 18aa462c75c..77436bc8a70 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-01-28T16:30:15.633Z", + "timestamp": "2026-02-06T14:30:42.270Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 9b0da76504c..18b2fe9488d 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-01-28T16:30:16.296Z", + "timestamp": "2026-02-06T14:30:42.923Z", "disclosures": [] } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index e222f7a1ae2..50ba1ffe211 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-01-28T16:30:16.642Z", + "timestamp": "2026-02-06T14:30:43.245Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index b437207d105..a74871556f4 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-01-28T16:30:16.693Z", + "timestamp": "2026-02-06T14:30:43.318Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index 66796571f18..4c6bd4668e4 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-01-28T16:30:16.716Z", + "timestamp": "2026-02-06T14:30:43.342Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index 22dc25ff479..60e7f9d11b7 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-01-28T16:30:16.770Z", + "timestamp": "2026-02-06T14:30:43.375Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index 5efd286beb6..1bf4595c57a 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-01-28T16:30:16.842Z", + "timestamp": "2026-02-06T14:30:43.394Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 4117148b7b5..e35881c947d 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-01-28T16:30:17.398Z", + "timestamp": "2026-02-06T14:30:44.045Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 1bf45f51e78..381e5419aeb 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-01-28T16:30:17.607Z", + "timestamp": "2026-02-06T14:30:44.259Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index b4bc9e17f6d..ebf244abad1 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-01-28T16:30:17.803Z", + "timestamp": "2026-02-06T14:30:44.438Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index d0e9d6e3281..6d6dbfa7d57 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-01-28T16:30:17.913Z", + "timestamp": "2026-02-06T14:30:44.555Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index 4fdd963b192..801d6f9b6e4 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://www.gamoshi.com/disclosures-client-storage.json": { - "timestamp": "2026-01-28T16:30:18.136Z", + "timestamp": "2026-02-06T14:30:45.142Z", "disclosures": [] } }, diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index 6effe5fa731..7990ef14045 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-01-28T16:30:18.258Z", + "timestamp": "2026-02-06T14:30:45.189Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 997f9f7bc1a..376dd089c16 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-01-28T16:30:18.259Z", + "timestamp": "2026-02-06T14:30:45.191Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 6b6d92bcf93..1329a2365eb 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-01-28T16:30:18.282Z", + "timestamp": "2026-02-06T14:30:45.215Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index ae4df482acf..4c60b8e8674 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-01-28T16:30:18.305Z", + "timestamp": "2026-02-06T14:30:45.235Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index f5735825ecb..cab476d46ae 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-01-28T16:30:18.450Z", + "timestamp": "2026-02-06T14:30:45.299Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index c0fb0c8617e..0bf26594331 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-01-28T16:30:18.502Z", + "timestamp": "2026-02-06T14:30:45.362Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index b24598c78f8..d341008fa29 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-01-28T16:30:18.608Z", + "timestamp": "2026-02-06T14:30:45.531Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index 4342f2840f2..7cf12b4f36a 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-01-28T16:30:18.608Z", + "timestamp": "2026-02-06T14:30:45.532Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index b812dcc3411..3ac996b810e 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-01-28T16:30:18.857Z", + "timestamp": "2026-02-06T14:30:45.792Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index 822336b6feb..60ab3d9a258 100644 --- a/metadata/modules/id5IdSystem.json +++ b/metadata/modules/id5IdSystem.json @@ -2,8 +2,250 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://id5-sync.com/tcf/disclosures.json": { - "timestamp": "2026-01-28T16:30:19.113Z", - "disclosures": [] + "timestamp": "2026-02-06T14:30:46.052Z", + "disclosures": [ + { + "identifier": "id5id", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_exp", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_last", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_last_exp", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_consent_data", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_consent_data_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_pd_{partnerId}", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_pd_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_pd", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_privacy", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_privacy_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_{partnerId}_nb", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_{partnerId}_nb_exp", + "type": "web", + "maxAgeSeconds": 7776000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_v2_{cacheId}", + "type": "web", + "maxAgeSeconds": 1296000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_v2_signature", + "type": "web", + "maxAgeSeconds": 1296000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_extensions", + "type": "web", + "maxAgeSeconds": 28800, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_segments_{partnerId}", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5id_cached_segments_{partnerId}_exp", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5_trueLink_privacy", + "type": "web", + "maxAgeSeconds": 2592000, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-ts", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-redirect-timestamp", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-redirect-fail", + "type": "cookie", + "maxAgeSeconds": 604800, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-tl-optout", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-true-link", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-true-link-refresh", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + }, + { + "identifier": "id5-true-link-refresh-exp", + "type": "cookie", + "maxAgeSeconds": 7776000, + "cookieRefresh": true, + "purposes": [ + 1, + 3 + ] + } + ] } }, "components": [ diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index e0da9afd224..1fccb000f8d 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-01-28T16:30:19.391Z", + "timestamp": "2026-02-06T14:30:46.346Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index 9872fe179fe..6ce5dbf3526 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-01-28T16:30:19.410Z", + "timestamp": "2026-02-06T14:30:46.365Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index ff872bf7c59..2c019a4f264 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-01-28T16:30:19.701Z", + "timestamp": "2026-02-06T14:30:46.639Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 93613334ee6..63212351bfd 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-01-28T16:30:20.078Z", + "timestamp": "2026-02-06T14:30:46.962Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index 12ac730cfab..06e1fc9d7f9 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-01-28T16:30:20.079Z", + "timestamp": "2026-02-06T14:30:46.963Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index 08318bac5bf..3854789c3b9 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-01-28T16:30:20.115Z", + "timestamp": "2026-02-06T14:30:47.037Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 3e419eef19a..184c8014738 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-01-28T16:30:20.139Z", + "timestamp": "2026-02-06T14:30:47.065Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index 63fcebf8060..7e8e5efc0e9 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-01-28T16:30:20.189Z", + "timestamp": "2026-02-06T14:30:47.125Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 4f8ff0b1227..c220378894a 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-01-28T16:30:20.533Z", + "timestamp": "2026-02-06T14:30:47.469Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index 35f6279a5b8..62bdfb810e8 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-01-28T16:30:21.016Z", + "timestamp": "2026-02-06T14:30:47.992Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index b83e3eb1d63..87ba03f761d 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-01-28T16:30:21.288Z", + "timestamp": "2026-02-06T14:30:48.991Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index 42a09fe26b8..380af8c19bc 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-01-28T16:30:21.799Z", + "timestamp": "2026-02-06T14:30:49.494Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index c5f0094a6b1..74d1b167ab8 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-01-28T16:30:21.822Z", + "timestamp": "2026-02-06T14:30:49.511Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index 96032f6827e..f70ff64a729 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-01-28T16:30:21.990Z", + "timestamp": "2026-02-06T14:30:49.678Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index 65251cb83cd..7d3010f7495 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-01-28T16:30:22.016Z", + "timestamp": "2026-02-06T14:30:49.695Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index 32782e737c0..29a2c141e58 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-01-28T16:30:22.072Z", + "timestamp": "2026-02-06T14:30:49.759Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-01-28T16:30:22.103Z", + "timestamp": "2026-02-06T14:30:49.817Z", "disclosures": [] } }, diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index 1cb74699212..ccfd9049d10 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-01-28T16:30:22.104Z", + "timestamp": "2026-02-06T14:30:49.817Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index c7ec883ac04..746ed0b1ba8 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-01-28T16:30:22.118Z", + "timestamp": "2026-02-06T14:30:49.830Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 03011a99e3a..7c490e6b140 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-01-28T16:30:22.119Z", + "timestamp": "2026-02-06T14:30:49.830Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index 57dbea94060..a952168bd7c 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-01-28T16:30:22.147Z", + "timestamp": "2026-02-06T14:30:49.855Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 7bf465e8b28..8dc95169721 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-01-28T16:30:22.264Z", + "timestamp": "2026-02-06T14:30:49.987Z", "disclosures": [ { "identifier": "lotame_domain_check", diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 55ff987e50a..6aed5ee92ea 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-01-28T16:30:22.285Z", + "timestamp": "2026-02-06T14:30:50.043Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 0e9003e97bb..ca20780c5f9 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-01-28T16:30:22.706Z", + "timestamp": "2026-02-06T14:30:50.512Z", "disclosures": [] } }, diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index dcaaeb86fce..4f98ea92389 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-01-28T16:30:23.103Z", + "timestamp": "2026-02-06T14:30:50.857Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index 066fa719827..6e1cee13c94 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-01-28T16:30:23.277Z", + "timestamp": "2026-02-06T14:30:50.977Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index 8d3aa85d0cb..7dcda2a0ea7 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-01-28T16:30:23.420Z", + "timestamp": "2026-02-06T14:30:51.151Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index 26645889fa4..679fbaa85f3 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-01-28T16:30:23.465Z", + "timestamp": "2026-02-06T14:30:51.166Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index a270ee3d116..bb067395315 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-01-28T16:30:23.466Z", + "timestamp": "2026-02-06T14:30:51.166Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index f240f43bcc6..add1e9abc41 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-01-28T16:30:23.528Z", + "timestamp": "2026-02-06T14:30:51.241Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index 60aeed6e7e0..f72cce4d6ed 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-01-28T16:30:23.849Z", + "timestamp": "2026-02-06T14:30:51.516Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:23.898Z", + "timestamp": "2026-02-06T14:30:51.678Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index d3f0d0f1e80..06a76111d44 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-01-28T16:30:23.940Z", + "timestamp": "2026-02-06T14:30:51.726Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index 6de647d3359..9e45a580a37 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-01-28T16:30:24.472Z", + "timestamp": "2026-02-06T14:30:52.272Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index c59d0acffd1..b1ce0f730d5 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-01-28T16:30:24.565Z", + "timestamp": "2026-02-06T14:30:52.337Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index 3f4ae1d1507..82bfa410234 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-01-28T16:30:24.565Z", + "timestamp": "2026-02-06T14:30:52.338Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index 33d4ba2e97e..5e15b8ec376 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-01-28T16:30:24.567Z", + "timestamp": "2026-02-06T14:30:52.338Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index 721444fc081..cd0b9867bdf 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-01-28T16:30:24.587Z", + "timestamp": "2026-02-06T14:30:52.361Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index 4c8248d6f80..e6669210e52 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-01-28T16:30:24.642Z", + "timestamp": "2026-02-06T14:30:52.417Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index 2aaa9ba6fbe..77fe7c78637 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-01-28T16:30:24.662Z", + "timestamp": "2026-02-06T14:30:52.437Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index a64f5821912..e863c308a77 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-01-28T16:30:24.690Z", + "timestamp": "2026-02-06T14:30:52.457Z", "disclosures": [] } }, diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index 397bbfb971d..c501744784e 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-01-28T16:30:24.691Z", + "timestamp": "2026-02-06T14:30:52.457Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index b2716873663..2ce0c474d07 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-01-28T16:30:24.694Z", + "timestamp": "2026-02-06T14:30:52.458Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index 2edcc2b20c9..5601803b583 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-01-28T16:30:25.034Z", + "timestamp": "2026-02-06T14:30:52.769Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index 2bdde04b806..6d9a056c546 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-01-28T16:30:25.072Z", + "timestamp": "2026-02-06T14:30:52.793Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index 4a1acbcebee..de42314a766 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-01-28T16:30:25.073Z", + "timestamp": "2026-02-06T14:30:52.793Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index 3235d22633f..c63566db548 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-01-28T16:30:25.140Z", + "timestamp": "2026-02-06T14:30:52.880Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 8c05a5de13c..c59498d7286 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-01-28T16:30:26.058Z", + "timestamp": "2026-02-06T14:30:53.696Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2026-01-28T16:30:25.421Z", + "timestamp": "2026-02-06T14:30:52.930Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:25.445Z", + "timestamp": "2026-02-06T14:30:52.956Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:25.665Z", + "timestamp": "2026-02-06T14:30:53.296Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,7 +46,7 @@ ] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2026-01-28T16:30:25.665Z", + "timestamp": "2026-02-06T14:30:53.296Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -61,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2026-01-28T16:30:25.684Z", + "timestamp": "2026-02-06T14:30:53.313Z", "disclosures": [] } }, diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index e87c1602d61..398bdfbf089 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-01-28T16:30:26.059Z", + "timestamp": "2026-02-06T14:30:53.697Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 63188ce57d5..47c3941614d 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-01-28T16:30:26.074Z", + "timestamp": "2026-02-06T14:30:53.713Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index 1191fd80de1..6aa638e72e8 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-01-28T16:30:27.691Z", + "timestamp": "2026-02-06T14:30:55.463Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index 3b044f32034..64c3fd4b836 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-01-28T16:30:28.031Z", + "timestamp": "2026-02-06T14:30:55.793Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index cb9afb31fb1..162210cd45c 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-01-28T16:30:28.095Z", + "timestamp": "2026-02-06T14:30:55.845Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index e0f6d4fa074..a4993aa24e9 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-01-28T16:30:28.157Z", + "timestamp": "2026-02-06T14:30:55.910Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 3d0c4ca8dee..fc77e7942f5 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-01-28T16:30:28.158Z", + "timestamp": "2026-02-06T14:30:55.911Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index e84a0376dac..43a909192d3 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-01-28T16:30:28.487Z", + "timestamp": "2026-02-06T14:30:56.264Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index 6d6fa2c2c23..181fe929290 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-01-28T16:30:28.525Z", + "timestamp": "2026-02-06T14:30:56.304Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index 76226db2c54..0671f2f9d1b 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-01-28T16:30:28.576Z", + "timestamp": "2026-02-06T14:30:56.360Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index 1e17c7686fd..2caeb173977 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-01-28T16:30:28.625Z", + "timestamp": "2026-02-06T14:30:56.554Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index a894055a0b3..3ac44009a9d 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-01-28T16:30:28.705Z", + "timestamp": "2026-02-06T14:30:56.602Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index 0ae9bf725ab..b2c3a3570a4 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-01-28T16:30:28.971Z", + "timestamp": "2026-02-06T14:30:56.869Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index 5e5a4cd4aeb..5de0614a637 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-01-28T16:30:29.295Z", + "timestamp": "2026-02-06T14:30:57.162Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index ea70878496b..3314ec181ad 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-01-28T16:30:29.606Z", + "timestamp": "2026-02-06T14:30:57.265Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index d83173c0933..251229e5281 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-01-28T16:30:29.871Z", + "timestamp": "2026-02-06T14:30:57.474Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json index 271639b6bcc..b03acbad7b8 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-01-28T16:30:29.889Z", + "timestamp": "2026-02-06T14:30:57.494Z", "disclosures": [ { "identifier": "panxo_uid", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index f845d868039..1040cb50c27 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-01-28T16:30:30.114Z", + "timestamp": "2026-02-06T14:30:57.921Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index 8d762cfa8ac..ae012106979 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-01-28T16:30:30.530Z", + "timestamp": "2026-02-06T14:30:58.428Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index b3183046ced..6f05af9d2b2 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-01-28T16:30:30.723Z", + "timestamp": "2026-02-06T14:30:58.703Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index 404e5160044..d75f0bd999a 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-01-28T16:30:30.724Z", + "timestamp": "2026-02-06T14:30:58.704Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index e1cfc53a2be..ea316812f29 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-01-28T16:30:30.771Z", + "timestamp": "2026-02-06T14:30:58.773Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 27215922c30..389211945a8 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-01-28T16:29:58.199Z", + "timestamp": "2026-02-06T14:30:25.250Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-01-28T16:29:58.213Z", + "timestamp": "2026-02-06T14:30:25.252Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index 051accb3c62..ab023bd4ecc 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-01-28T16:30:30.948Z", + "timestamp": "2026-02-06T14:30:58.944Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index 0d1691488d9..e05a5750f03 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-01-28T16:30:30.998Z", + "timestamp": "2026-02-06T14:30:59.181Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index 1221f3c8eb4..4083a0bb464 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-01-28T16:30:30.998Z", + "timestamp": "2026-02-06T14:30:59.181Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index f6bd76fdd1b..82304f61384 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-01-28T16:30:31.059Z", + "timestamp": "2026-02-06T14:30:59.240Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index fb02ded585d..10505045ff7 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-01-28T16:30:31.543Z", + "timestamp": "2026-02-06T14:30:59.707Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index 607009f53ac..64bb5ffab84 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-01-28T16:30:31.544Z", + "timestamp": "2026-02-06T14:30:59.708Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index c57d7c0d7c4..463037c5a5f 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-01-28T16:30:31.576Z", + "timestamp": "2026-02-06T14:30:59.726Z", "disclosures": [] } }, diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index 4bacd53227a..d3f2cc7ee59 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-01-28T16:30:31.577Z", + "timestamp": "2026-02-06T14:30:59.729Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index 426abc3ac5c..69c0e462a27 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-01-28T16:30:31.593Z", + "timestamp": "2026-02-06T14:30:59.748Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json index 6c28e90ab63..de44c0143bf 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-01-28T16:30:31.773Z", + "timestamp": "2026-02-06T14:30:59.931Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index 6b3f923e892..6dbf10fc8bb 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-01-28T16:30:31.773Z", + "timestamp": "2026-02-06T14:30:59.932Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index 6b75cbc9276..e5df3e4b05e 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-01-28T16:30:32.145Z", + "timestamp": "2026-02-06T14:31:00.371Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index 08eedf926e5..b0f1623d592 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-01-28T16:30:32.375Z", + "timestamp": "2026-02-06T14:31:01.523Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index 703482de16d..9a0222f5934 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-01-28T16:30:32.683Z", + "timestamp": "2026-02-06T14:31:01.708Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index 89d23bb6652..6b964535ce7 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-01-28T16:30:32.722Z", + "timestamp": "2026-02-06T14:31:01.749Z", "disclosures": [] } }, diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index 3f45c788da2..849e6939e78 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-01-28T16:30:32.781Z", + "timestamp": "2026-02-06T14:31:01.772Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json index 29fdb8b0198..5018f0d66f7 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-01-28T16:30:32.795Z", + "timestamp": "2026-02-06T14:31:01.792Z", "disclosures": [] } }, diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index 31d017d8c39..4a825c9e721 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-01-28T16:30:32.929Z", + "timestamp": "2026-02-06T14:31:01.869Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index db4f6fbb7d4..c0828fecfaa 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-01-28T16:30:33.193Z", + "timestamp": "2026-02-06T14:31:02.113Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index 15a580963ff..9e7db30fb14 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-01-28T16:30:33.264Z", + "timestamp": "2026-02-06T14:31:02.167Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-01-28T16:30:33.264Z", + "timestamp": "2026-02-06T14:31:02.167Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 437a19779b1..7cfb9cf6ab4 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-01-28T16:30:33.264Z", + "timestamp": "2026-02-06T14:31:02.168Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index 20c7edbd123..e45ec11be5c 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-01-28T16:30:33.289Z", + "timestamp": "2026-02-06T14:31:02.186Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index e8f991d7e6d..2d3b69626fa 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-01-28T16:30:33.692Z", + "timestamp": "2026-02-06T14:31:02.472Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index 95100416ca0..78659ada722 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-01-28T16:30:33.929Z", + "timestamp": "2026-02-06T14:31:02.728Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 19bce2ec18b..3dc900a8b4a 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-01-28T16:30:33.953Z", + "timestamp": "2026-02-06T14:31:02.744Z", "disclosures": null } }, diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index fe8d650a43f..050431508c0 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-01-28T16:30:36.548Z", + "timestamp": "2026-02-06T14:31:05.338Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index dc5a53845ed..c5c50a70b1c 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-01-28T16:30:36.576Z", + "timestamp": "2026-02-06T14:31:05.364Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index e03a5cffba7..2fca80acc4a 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-01-28T16:30:36.576Z", + "timestamp": "2026-02-06T14:31:05.364Z", "disclosures": [] } }, diff --git a/metadata/modules/setupadBidAdapter.json b/metadata/modules/setupadBidAdapter.json index 9114eb7ffec..ec357f4ee2e 100644 --- a/metadata/modules/setupadBidAdapter.json +++ b/metadata/modules/setupadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cookies.stpd.cloud/disclosures.json": { - "timestamp": "2026-01-28T16:30:36.628Z", + "timestamp": "2026-02-06T14:31:05.430Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index 9c5e2a82b2a..7ca3381d3a9 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-01-28T16:30:36.792Z", + "timestamp": "2026-02-06T14:31:05.617Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index deda98910f7..568e092c809 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-01-28T16:30:36.949Z", + "timestamp": "2026-02-06T14:31:05.800Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index 5d835a1ba39..c6ff5df02c8 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-01-28T16:30:36.949Z", + "timestamp": "2026-02-06T14:31:05.801Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index 7dcee4cef53..993b9968529 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-01-28T16:30:36.971Z", + "timestamp": "2026-02-06T14:31:05.827Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index 21743e158e4..8cba8c7f296 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-01-28T16:30:37.398Z", + "timestamp": "2026-02-06T14:31:06.289Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index 292ad9c1408..8fa8db1c234 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-01-28T16:30:37.414Z", + "timestamp": "2026-02-06T14:31:06.304Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index 35d7af131ec..40b7972f55e 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-01-28T16:30:37.710Z", + "timestamp": "2026-02-06T14:31:06.599Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index a1ed3e1f8bd..bd1eee175c1 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-01-28T16:30:37.818Z", + "timestamp": "2026-02-06T14:31:06.691Z", "disclosures": [] } }, diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 2bbf3c45927..c235ca9052d 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-01-28T16:30:37.819Z", + "timestamp": "2026-02-06T14:31:06.694Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index c56d5f330e6..98f9a652ab5 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-01-28T16:30:37.844Z", + "timestamp": "2026-02-06T14:31:06.715Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index 5c3b44c8296..1ceac57db43 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-01-28T16:30:37.886Z", + "timestamp": "2026-02-06T14:31:06.756Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index 68d1c0c2815..d5ca0d05f33 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-01-28T16:30:38.328Z", + "timestamp": "2026-02-06T14:31:07.372Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index b41bae638fe..3cccdfc58e0 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-01-28T16:30:38.465Z", + "timestamp": "2026-02-06T14:31:07.449Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index bef53619e19..b77fbe98b11 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-01-28T16:30:38.695Z", + "timestamp": "2026-02-06T14:31:07.697Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index ae4dc05951d..62cf4b62595 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-01-28T16:30:38.930Z", + "timestamp": "2026-02-06T14:31:07.928Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index 4f549fdb2e4..67eaa14f9bb 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-01-28T16:30:38.952Z", + "timestamp": "2026-02-06T14:31:07.947Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index ccc832d962a..1df0d91d751 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-01-28T16:30:39.228Z", + "timestamp": "2026-02-06T14:31:08.233Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index db6d59ff097..37ff0a3962e 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-01-28T16:30:39.834Z", + "timestamp": "2026-02-06T14:31:08.788Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index f0049ce614b..c0bee206669 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-01-28T16:30:39.835Z", + "timestamp": "2026-02-06T14:31:08.789Z", "disclosures": [ { "identifier": "sa-camp-*", @@ -62,6 +62,17 @@ 4 ] }, + { + "identifier": "sa-user-id-v4", + "type": "cookie", + "maxAgeSeconds": 31536000, + "cookieRefresh": false, + "purposes": [ + 1, + 3, + 4 + ] + }, { "identifier": "sa-user-id", "type": "web", @@ -84,6 +95,17 @@ 4 ] }, + { + "identifier": "sa-user-id-v4", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1, + 3, + 4 + ] + }, { "identifier": "sa-camp-*", "type": "web", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index b2288e475a3..5f8395c337a 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-01-28T16:30:39.868Z", + "timestamp": "2026-02-06T14:31:08.823Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index d665b481f92..dfc4fbf2d55 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-01-28T16:30:39.889Z", + "timestamp": "2026-02-06T14:31:08.840Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index d1ce12c1ae4..61d27bbf32e 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-01-28T16:30:40.266Z", + "timestamp": "2026-02-06T14:31:09.177Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index 77f960e3892..da5ddf414a2 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-01-28T16:30:40.913Z", + "timestamp": "2026-02-06T14:31:09.818Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index 07e38d4e2f3..6a42ffd2e45 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-01-28T16:30:41.174Z", + "timestamp": "2026-02-06T14:31:09.843Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index 5192f0b5a53..d5c707ead2d 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-01-28T16:30:41.833Z", + "timestamp": "2026-02-06T14:31:10.305Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index a3cb6303200..7098f885be9 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-01-28T16:30:41.834Z", + "timestamp": "2026-02-06T14:31:10.305Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index a487b12cb7d..a6858ca9676 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-01-28T16:30:41.858Z", + "timestamp": "2026-02-06T14:31:10.345Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index ec93fdb78b9..8b8c1388d5e 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-01-28T16:30:41.884Z", + "timestamp": "2026-02-06T14:31:10.374Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index daed112213d..7bbb7655f89 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-01-28T16:30:41.885Z", + "timestamp": "2026-02-06T14:31:10.374Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index 8e927eaff33..e48aa0da86a 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-01-28T16:30:41.907Z", + "timestamp": "2026-02-06T14:31:10.394Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index 704d75aadae..4bebd9ead62 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-01-28T16:30:41.907Z", + "timestamp": "2026-02-06T14:31:10.394Z", "disclosures": [] } }, diff --git a/metadata/modules/teqBlazeSalesAgentBidAdapter.json b/metadata/modules/teqBlazeSalesAgentBidAdapter.json new file mode 100644 index 00000000000..2442df240d1 --- /dev/null +++ b/metadata/modules/teqBlazeSalesAgentBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "teqBlazeSalesAgent", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index 647cab4a5cc..87b702ad1f0 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-01-28T16:30:41.941Z", + "timestamp": "2026-02-06T14:31:10.450Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index 95987763bde..d348a1ebf38 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-01-28T16:29:58.214Z", + "timestamp": "2026-02-06T14:30:25.252Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index d008f59f2dd..7bdbc493d58 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-01-28T16:30:41.959Z", + "timestamp": "2026-02-06T14:31:10.470Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index 94cc97bcf8a..06b55c5102a 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-01-28T16:30:41.986Z", + "timestamp": "2026-02-06T14:31:10.486Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index d6d3b8cebec..79c830c75f5 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-01-28T16:30:42.047Z", + "timestamp": "2026-02-06T14:31:10.517Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index 9d23eddf753..fb046a0bbec 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-01-28T16:30:42.047Z", + "timestamp": "2026-02-06T14:31:10.517Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index fefaee8ca12..20543d0e571 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-01-28T16:30:42.135Z", + "timestamp": "2026-02-06T14:31:10.583Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index eab5cb0d139..66072da4aa9 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-01-28T16:30:42.159Z", + "timestamp": "2026-02-06T14:31:10.636Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index c036a28e618..151095b8c9f 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-01-28T16:30:42.227Z", + "timestamp": "2026-02-06T14:31:10.652Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index e2175b3f549..a8be804fe86 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-01-28T16:30:42.227Z", + "timestamp": "2026-02-06T14:31:10.653Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index 4cb33e6f4a1..305658c2db7 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-01-28T16:29:58.216Z", + "timestamp": "2026-02-06T14:30:25.254Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index 071faa17400..bdb623039d0 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-01-28T16:30:42.227Z", + "timestamp": "2026-02-06T14:31:10.654Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index 2df7ca96f1b..0c6d65dc895 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-01-28T16:30:42.229Z", + "timestamp": "2026-02-06T14:31:10.654Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index ca2ae6a8883..a057fd95c35 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-01-28T16:29:58.215Z", + "timestamp": "2026-02-06T14:30:25.253Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index 9141c9dceda..9a5bb4518c6 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-01-28T16:30:42.229Z", + "timestamp": "2026-02-06T14:31:10.655Z", "disclosures": [] } }, diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index 8cfb42a6329..c42ec8127ea 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-01-28T16:30:42.541Z", + "timestamp": "2026-02-06T14:31:10.856Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index a15352d7209..df0174fca4d 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-01-28T16:30:42.610Z", + "timestamp": "2026-02-06T14:31:10.919Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index a9ba5b1ea69..fb97dba417f 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-01-28T16:30:42.721Z", + "timestamp": "2026-02-06T14:31:11.037Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 82a358c4252..cef609b5c04 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-01-28T16:30:42.722Z", + "timestamp": "2026-02-06T14:31:11.038Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index f981ce6b7d8..aeb22ba00b5 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-01-28T16:30:42.933Z", + "timestamp": "2026-02-06T14:31:11.202Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index 534d5aa909f..cffb548d671 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-01-28T16:30:43.350Z", + "timestamp": "2026-02-06T14:31:11.227Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 98b4bf97df2..a7a67693e92 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-01-28T16:30:43.350Z", + "timestamp": "2026-02-06T14:31:11.227Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index 82f8197b440..a0242851081 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-01-28T16:30:43.368Z", + "timestamp": "2026-02-06T14:31:11.243Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 942e28d93a9..93c8007e410 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://weborama.com/deviceStorage.json": { - "timestamp": "2026-01-28T16:30:43.680Z", + "timestamp": "2026-02-06T14:31:11.547Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index bf17a459ac4..059bedc785c 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-01-28T16:30:43.935Z", + "timestamp": "2026-02-06T14:31:11.801Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index 70f246f825d..d14a0812f0d 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-01-28T16:30:44.341Z", + "timestamp": "2026-02-06T14:31:12.205Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index 546bfd19305..832ffe58cda 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-01-28T16:30:44.342Z", + "timestamp": "2026-02-06T14:31:12.206Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index d311669356b..d16d940a366 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-01-28T16:30:44.472Z", + "timestamp": "2026-02-06T14:31:12.316Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index 0f2052e3cf4..db807c44814 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-01-28T16:30:44.497Z", + "timestamp": "2026-02-06T14:31:12.340Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index 6105ace2968..89f08fa701e 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-01-28T16:30:44.585Z", + "timestamp": "2026-02-06T14:31:12.423Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index 274bd4c75f0..a8b4b95bb58 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-01-28T16:30:44.708Z", + "timestamp": "2026-02-06T14:31:12.568Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index e51e6fd80ec..fcd9e16e6c7 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-01-28T16:30:44.784Z", + "timestamp": "2026-02-06T14:31:12.668Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index 007a8ba611d..5208c332da2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.24.0-pre", + "version": "10.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.24.0-pre", + "version": "10.24.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", @@ -7878,9 +7878,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "funding": [ { "type": "opencollective", @@ -27337,9 +27337,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==" + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==" }, "chai": { "version": "4.4.1", diff --git a/package.json b/package.json index c7ca0275009..594d615c1f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.24.0-pre", + "version": "10.24.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 7e17a89f2a93df8fd1909afb46ff26c0c36d0bd3 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 6 Feb 2026 14:32:13 +0000 Subject: [PATCH 027/105] Increment version to 10.25.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 5208c332da2..f10860da566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.24.0", + "version": "10.25.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.24.0", + "version": "10.25.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 594d615c1f8..14cf63649cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.24.0", + "version": "10.25.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From ba3f9d830e715d59a23c0b221705c15de1f2172e Mon Sep 17 00:00:00 2001 From: Deepthi Neeladri Date: Fri, 6 Feb 2026 22:34:02 +0530 Subject: [PATCH 028/105] Yahoo Ads Bid Adapter: Add Transaction ID (TID) Support (#14403) * tesing * adding tests * md file * Update yahooAdsBidAdapter_spec.js * Fix formatting in yahooAdsBidAdapter_spec.js * Fix casing for enableTIDs in documentation --------- Co-authored-by: dsravana Co-authored-by: Patrick McCann --- modules/yahooAdsBidAdapter.js | 1 + modules/yahooAdsBidAdapter.md | 35 ++++++++++ test/spec/modules/yahooAdsBidAdapter_spec.js | 70 +++++++++++++++++++- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/modules/yahooAdsBidAdapter.js b/modules/yahooAdsBidAdapter.js index b2b92c6ba95..16c9365b5d7 100644 --- a/modules/yahooAdsBidAdapter.js +++ b/modules/yahooAdsBidAdapter.js @@ -288,6 +288,7 @@ function generateOpenRtbObject(bidderRequest, bid) { } }, source: { + tid: bidderRequest.ortb2?.source?.tid, ext: { hb: 1, adapterver: ADAPTER_VERSION, diff --git a/modules/yahooAdsBidAdapter.md b/modules/yahooAdsBidAdapter.md index df9b71b2314..a26d24338c5 100644 --- a/modules/yahooAdsBidAdapter.md +++ b/modules/yahooAdsBidAdapter.md @@ -18,6 +18,7 @@ The Yahoo Advertising Bid Adapter is an OpenRTB interface that consolidates all * User ID Modules - ConnectId and others * First Party Data (ortb2 & ortb2Imp) * Custom TTL (time to live) +* Transaction ID (TID) support via ortb2.source.tid # Adapter Aliases Whilst the primary bidder code for this bid adapter is `yahooAds`, the aliases `yahoossp` and `yahooAdvertising` can be used to enable this adapter. If you wish to set Prebid configuration specifically for this bid adapter, then the configuration key _must_ match the used bidder code. All examples in this documentation use the primiry bidder code, but switching `yahooAds` with one of the relevant aliases may be required for your setup. Let's take [setting the request mode](#adapter-request-mode) as an example; if you used the `yahoossp` alias, then the corresponding `setConfig` API call would look like this: @@ -562,6 +563,40 @@ const adUnits = [{ ] ``` +## Transaction ID (TID) Support +The Yahoo Advertising bid adapter supports reading publisher-provided transaction IDs from `ortb2.source.tid` and including them in the OpenRTB request. This enables better bid request tracking and deduplication across the supply chain. + +**Important:** To use transaction IDs, you must enable TIDs in your Prebid configuration by setting `enableTIDs: true`. + +### Global Transaction ID (applies to all bidders) +```javascript +pbjs.setConfig({ + enableTIDs: true, // Required for TID support + ortb2: { + source: { + tid: "transaction-id-12345" + } + } +}); +``` + +### Bidder-Specific Transaction ID (Yahoo Ads only) +```javascript +pbjs.setBidderConfig({ + bidders: ['yahooAds'], + config: { + enableTIDs: true, // Required for TID support + ortb2: { + source: { + tid: "yahoo-specific-tid-67890" + } + } + } +}); +``` + +**Note:** If `enableTIDs` is not set to `true`, the transaction ID will not be available to the adapter, even if `ortb2.source.tid` is configured. When TID is not provided or not enabled, the adapter will not include the `source.tid` field in the OpenRTB request. + # Optional: Bidder bidOverride Parameters The Yahoo Advertising bid adapter allows passing override data to the outbound bid-request that overrides First Party Data. **Important!** We highly recommend using prebid modules to pass data instead of bidder speicifc overrides. diff --git a/test/spec/modules/yahooAdsBidAdapter_spec.js b/test/spec/modules/yahooAdsBidAdapter_spec.js index ad1e428aafd..093df9355f1 100644 --- a/test/spec/modules/yahooAdsBidAdapter_spec.js +++ b/test/spec/modules/yahooAdsBidAdapter_spec.js @@ -992,7 +992,6 @@ describe('Yahoo Advertising Bid Adapter:', () => { {source: 'neustar.biz', uids: [{id: 'fabrickId_FROM_USER_ID_MODULE', atype: 1}]} ]; const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user.ext.eids).to.deep.equal(validBidRequests[0].userIdAsEids); }); @@ -1034,6 +1033,7 @@ describe('Yahoo Advertising Bid Adapter:', () => { }); expect(data.source).to.deep.equal({ + tid: undefined, ext: { hb: 1, adapterver: ADAPTER_VERSION, @@ -1056,6 +1056,74 @@ describe('Yahoo Advertising Bid Adapter:', () => { expect(data.cur).to.deep.equal(['USD']); }); + it('should not include source.tid when publisher does not provide it', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.be.undefined; + }); + + it('should include source.tid from bidderRequest.ortb2.source.tid when provided', () => { + const ortb2 = { + source: { + tid: 'test-transaction-id-12345' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.equal('test-transaction-id-12345'); + }); + + it('should read source.tid from global ortb2 config when enableTids is true', () => { + const ortb2 = { + source: { + tid: 'global-tid-67890' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.equal('global-tid-67890'); + expect(data.source.ext.hb).to.equal(1); + expect(data.source.fd).to.equal(1); + }); + + it('should include source.tid alongside existing source.ext properties', () => { + const ortb2 = { + source: { + tid: 'test-tid-with-ext' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + expect(data.source).to.have.property('tid'); + expect(data.source.tid).to.equal('test-tid-with-ext'); + expect(data.source.ext).to.deep.equal({ + hb: 1, + adapterver: ADAPTER_VERSION, + prebidver: PREBID_VERSION, + integration: { + name: INTEGRATION_METHOD, + ver: PREBID_VERSION + } + }); + expect(data.source.fd).to.equal(1); + }); + + it('should handle empty source.tid gracefully', () => { + const ortb2 = { + source: { + tid: '' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + bidderRequest.ortb2 = ortb2; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.source.tid).to.equal(''); + }); + it('should generate a valid openRTB imp.ext object in the bid-request', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); const bid = validBidRequests[0]; From c091e0ce79babb4624136ffcd9e91ea988fb52a5 Mon Sep 17 00:00:00 2001 From: robin-crazygames Date: Mon, 9 Feb 2026 13:59:49 +0100 Subject: [PATCH 029/105] gam video module: Include us_privacy based on gpp when downloading VAST for the IMA player (#14424) * Include us_privacy based on gpp when downloading VAST for the IMA player IMA player has no support for GPP, but does support uspapi. When on `window`, there is only `__gpp`, we can still pass a US Privacy string by deriving the string from the `__gpp` data, * Added tests for the retrieveUspInfoFromGpp method * Re-write the test to use the public API instead of internal methods * Deal with the option that the parsedSections contains sections which are arrays * Deal with possible null/missing values in the GPP parsedSection --- modules/gamAdServerVideo.js | 44 ++- test/spec/modules/gamAdServerVideo_spec.js | 297 +++++++++++++++++++++ 2 files changed, 340 insertions(+), 1 deletion(-) diff --git a/modules/gamAdServerVideo.js b/modules/gamAdServerVideo.js index 9613af93e4e..b97f4ff598c 100644 --- a/modules/gamAdServerVideo.js +++ b/modules/gamAdServerVideo.js @@ -28,7 +28,7 @@ import { fetch } from '../src/ajax.js'; import XMLUtil from '../libraries/xmlUtils/xmlUtils.js'; import {getGlobalVarName} from '../src/buildOptions.js'; -import { uspDataHandler } from '../src/consentHandler.js'; +import { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; /** * @typedef {Object} DfpVideoParams * @@ -299,6 +299,7 @@ export async function getVastXml(options, localCacheMap = vastLocalCache) { const video = adUnit?.mediaTypes?.video; const sdkApis = (video?.api || []).join(','); const usPrivacy = uspDataHandler.getConsentData?.(); + const gpp = gppDataHandler.getConsentData?.(); // Adding parameters required by ima if (config.getConfig('cache.useLocal') && window.google?.ima) { vastUrl = new URL(vastUrl); @@ -310,6 +311,12 @@ export async function getVastXml(options, localCacheMap = vastLocalCache) { } if (usPrivacy) { vastUrl.searchParams.set('us_privacy', usPrivacy); + } else if (gpp) { + // Extract an usPrivacy string from the GPP string if possible + const uspFromGpp = retrieveUspInfoFromGpp(gpp); + if (uspFromGpp) { + vastUrl.searchParams.set('us_privacy', uspFromGpp) + } } vastUrl = vastUrl.toString(); @@ -329,6 +336,41 @@ export async function getVastXml(options, localCacheMap = vastLocalCache) { return gamVastWrapper; } +/** + * Extract a US Privacy string from the GPP data + */ +function retrieveUspInfoFromGpp(gpp) { + if (!gpp) { + return undefined; + } + const parsedSections = gpp.gppData?.parsedSections; + if (parsedSections) { + if (parsedSections.uspv1) { + const usp = parsedSections.uspv1; + return `${usp.Version}${usp.Notice}${usp.OptOutSale}${usp.LspaCovered}` + } else { + let saleOptOut; + let saleOptOutNotice; + Object.values(parsedSections).forEach(parsedSection => { + (Array.isArray(parsedSection) ? parsedSection : [parsedSection]).forEach(ps => { + const sectionSaleOptOut = ps.SaleOptOut; + const sectionSaleOptOutNotice = ps.SaleOptOutNotice; + if (saleOptOut === undefined && saleOptOutNotice === undefined && sectionSaleOptOut != null && sectionSaleOptOutNotice != null) { + saleOptOut = sectionSaleOptOut; + saleOptOutNotice = sectionSaleOptOutNotice; + } + }); + }); + if (saleOptOut !== undefined && saleOptOutNotice !== undefined) { + const uspOptOutSale = saleOptOut === 0 ? '-' : saleOptOut === 1 ? 'Y' : 'N'; + const uspOptOutNotice = saleOptOutNotice === 0 ? '-' : saleOptOutNotice === 1 ? 'Y' : 'N'; + const uspLspa = uspOptOutSale === '-' && uspOptOutNotice === '-' ? '-' : 'Y'; + return `1${uspOptOutNotice}${uspOptOutSale}${uspLspa}`; + } + } + } + return undefined +} export async function getBase64BlobContent(blobUrl) { const response = await fetch(blobUrl); diff --git a/test/spec/modules/gamAdServerVideo_spec.js b/test/spec/modules/gamAdServerVideo_spec.js index a8ce01c1457..1004b4b5aae 100644 --- a/test/spec/modules/gamAdServerVideo_spec.js +++ b/test/spec/modules/gamAdServerVideo_spec.js @@ -17,6 +17,7 @@ import {AuctionIndex} from '../../../src/auctionIndex.js'; import { getVastXml } from '../../../modules/gamAdServerVideo.js'; import { server } from '../../mocks/xhr.js'; import { generateUUID } from '../../../src/utils.js'; +import { uspDataHandler, gppDataHandler } from '../../../src/consentHandler.js'; describe('The DFP video support module', function () { before(() => { @@ -870,4 +871,300 @@ describe('The DFP video support module', function () { .finally(config.resetConfig); server.respond(); }); + + describe('Retrieve US Privacy string from GPP when using the IMA player and downloading VAST XMLs', () => { + beforeEach(() => { + config.setConfig({cache: { useLocal: true }}); + // Install a fake IMA object, because the us_privacy is only set when IMA is available + window.google = { + ima: { + VERSION: '2.3.37' + } + } + }) + afterEach(() => { + config.resetConfig(); + }) + + async function obtainUsPrivacyInVastXmlRequest() { + const url = 'https://pubads.g.doubleclick.net/gampad/ads' + const bidCacheUrl = 'https://prebid-test-cache-server.org/cache?uuid=4536229c-eddb-45b3-a919-89d889e925aa'; + const gamWrapper = ( + `` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + server.respondWith(gamWrapper); + + const result = getVastXml({url, adUnit: {}, bid: {}}, []).then(() => { + const request = server.requests[0]; + const url = new URL(request.url); + return url.searchParams.get('us_privacy'); + }); + server.respond(); + + return result; + } + + function mockGpp(gpp) { + sandbox.stub(gppDataHandler, 'getConsentData').returns(gpp) + } + + function wrapParsedSectionsIntoGPPData(parsedSections) { + return { + gppData: { + parsedSections: parsedSections + } + } + } + + it('should use usp when available, even when gpp is available', async () => { + const usPrivacy = '1YYY'; + sandbox.stub(uspDataHandler, 'getConsentData').returns(usPrivacy); + mockGpp(wrapParsedSectionsIntoGPPData({ + "uspv1": { + "Version": 1, + "Notice": "Y", + "OptOutSale": "N", + "LspaCovered": "Y" + } + })); + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.equal(usPrivacy) + }) + + it('no us_privacy when neither usp nor gpp is present', async () => { + const usPrivacyFromRequqest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequqest).to.be.null; + }) + + it('can retrieve from usp section in gpp', async () => { + mockGpp(wrapParsedSectionsIntoGPPData({ + "uspv1": { + "Version": 1, + "Notice": "Y", + "OptOutSale": "N", + "LspaCovered": "Y" + } + })); + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.equal('1YNY') + }) + it('can retrieve from usnat section in gpp', async () => { + mockGpp(wrapParsedSectionsIntoGPPData({ + "usnat": { + "Version": 1, + "SharingNotice": 2, + "SaleOptOutNotice": 1, + "SharingOptOutNotice": 0, + "TargetedAdvertisingOptOutNotice": 2, + "SensitiveDataProcessingOptOutNotice": 1, + "SensitiveDataLimitUseNotice": 1, + "SaleOptOut": 1, + "SharingOptOut": 2, + "TargetedAdvertisingOptOut": 2, + "SensitiveDataProcessing": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "KnownChildSensitiveDataConsents": [ + 0, + 0, + 0 + ], + "PersonalDataConsents": 0, + "MspaCoveredTransaction": 1, + "MspaOptOutOptionMode": 0, + "MspaServiceProviderMode": 0, + "GpcSegmentType": 1, + "Gpc": false + } + })); + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.equal('1YYY'); + }) + it('can retrieve from usnat section in gpp when usnat is an array', async() => { + mockGpp(wrapParsedSectionsIntoGPPData({ + "usnat": [ + { + "Version": 1, + "SharingNotice": 2, + "SaleOptOutNotice": 1, + "SharingOptOutNotice": 1, + "TargetedAdvertisingOptOutNotice": 1, + "SensitiveDataProcessingOptOutNotice": 1, + "SensitiveDataLimitUseNotice": 0, + "SaleOptOut": 2, + "SharingOptOut": 2, + "TargetedAdvertisingOptOut": 2, + "PersonalDataConsents": 0, + "MspaCoveredTransaction": 0, + "MspaOptOutOptionMode": 0, + "MspaServiceProviderMode": 0, + }, { + "GpcSegmentType": 1, + "Gpc": false + } + ] + })) + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.equal('1YNY'); + }) + it('no us_privacy when either SaleOptOutNotice or SaleOptOut is missing', async () => { + // Missing SaleOptOutNotice + mockGpp(wrapParsedSectionsIntoGPPData({ + "usnat": { + "Version": 1, + "SharingNotice": 2, + "SharingOptOutNotice": 0, + "TargetedAdvertisingOptOutNotice": 2, + "SensitiveDataProcessingOptOutNotice": 1, + "SensitiveDataLimitUseNotice": 1, + "SaleOptOut": 1, + "SharingOptOut": 2, + "TargetedAdvertisingOptOut": 2, + "SensitiveDataProcessing": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "KnownChildSensitiveDataConsents": [ + 0, + 0, + 0 + ], + "PersonalDataConsents": 0, + "MspaCoveredTransaction": 1, + "MspaOptOutOptionMode": 0, + "MspaServiceProviderMode": 0, + "GpcSegmentType": 1, + "Gpc": false + } + })); + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.be.null; + }) + it('no us_privacy when either SaleOptOutNotice or SaleOptOut is null', async () => { + // null SaleOptOut + mockGpp(wrapParsedSectionsIntoGPPData({ + "usnat": { + "Version": 1, + "SharingNotice": 2, + "SaleOptOutNotice": 1, + "SharingOptOutNotice": 0, + "TargetedAdvertisingOptOutNotice": 2, + "SensitiveDataProcessingOptOutNotice": 1, + "SensitiveDataLimitUseNotice": 1, + "SaleOptOut": null, + "SharingOptOut": 2, + "TargetedAdvertisingOptOut": 2, + "SensitiveDataProcessing": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "KnownChildSensitiveDataConsents": [ + 0, + 0, + 0 + ], + "PersonalDataConsents": 0, + "MspaCoveredTransaction": 1, + "MspaOptOutOptionMode": 0, + "MspaServiceProviderMode": 0, + "GpcSegmentType": 1, + "Gpc": false + } + })); + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.be.null; + }) + + it('can retrieve from usca section in gpp', async () => { + mockGpp(wrapParsedSectionsIntoGPPData({ + "usca": { + "Version": 1, + "SaleOptOutNotice": 1, + "SharingOptOutNotice": 1, + "SensitiveDataLimitUseNotice": 1, + "SaleOptOut": 2, + "SharingOptOut": 2, + "SensitiveDataProcessing": [ + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "KnownChildSensitiveDataConsents": [ + 0, + 0 + ], + "PersonalDataConsents": 0, + "MspaCoveredTransaction": 2, + "MspaOptOutOptionMode": 0, + "MspaServiceProviderMode": 0, + "GpcSegmentType": 1, + "Gpc": false + }})); + + const usPrivacyFromRequest = await obtainUsPrivacyInVastXmlRequest(); + expect(usPrivacyFromRequest).to.equal('1YNY'); + }) + }); }); From ba1334c90242f2d6a69143069b915f6a50dbb7a4 Mon Sep 17 00:00:00 2001 From: optidigital-prebid <124287395+optidigital-prebid@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:53:35 +0100 Subject: [PATCH 030/105] Optidigital Bid Adapter: Adding ortb2 device, keywords, addtlConsent, gpid (#14383) * add gpp suport * update of the optidigital adapter * fix the lint issue * refactor ortb2 keywords trim logic --------- Co-authored-by: Dawid W Co-authored-by: Patrick McCann --- modules/optidigitalBidAdapter.js | 22 +++- .../modules/optidigitalBidAdapter_spec.js | 108 +++++++++++++++++- 2 files changed, 123 insertions(+), 7 deletions(-) diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js index 1330bf2054f..6529f02b5bc 100755 --- a/modules/optidigitalBidAdapter.js +++ b/modules/optidigitalBidAdapter.js @@ -63,7 +63,8 @@ export const spec = { imp: validBidRequests.map(bidRequest => buildImp(bidRequest, ortb2)), badv: ortb2.badv || deepAccess(validBidRequests[0], 'params.badv') || [], bcat: ortb2.bcat || deepAccess(validBidRequests[0], 'params.bcat') || [], - bapp: deepAccess(validBidRequests[0], 'params.bapp') || [] + bapp: deepAccess(validBidRequests[0], 'params.bapp') || [], + device: ortb2.device || {} } if (validBidRequests[0].auctionId) { @@ -82,10 +83,14 @@ export const spec = { const gdpr = deepAccess(bidderRequest, 'gdprConsent'); if (bidderRequest && gdpr) { const isConsentString = typeof gdpr.consentString === 'string'; + const isGdprApplies = typeof gdpr.gdprApplies === 'boolean'; payload.gdpr = { consent: isConsentString ? gdpr.consentString : '', - required: true + required: isGdprApplies ? gdpr.gdprApplies : false }; + if (gdpr?.addtlConsent) { + payload.gdpr.addtlConsent = gdpr.addtlConsent; + } } if (bidderRequest && !gdpr) { payload.gdpr = { @@ -120,10 +125,16 @@ export const spec = { } } + const ortb2SiteKeywords = (bidderRequest?.ortb2?.site?.keywords || '')?.split(',').map(k => k.trim()).filter(k => k !== '').join(','); + if (ortb2SiteKeywords) { + payload.site = payload.site || {}; + payload.site.keywords = ortb2SiteKeywords; + } + const payloadObject = JSON.stringify(payload); return { method: 'POST', - url: ENDPOINT_URL, + url: `${ENDPOINT_URL}/${payload.publisherId}`, data: payloadObject }; }, @@ -230,6 +241,11 @@ function buildImp(bidRequest, ortb2) { imp.battr = battr; } + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + if (gpid) { + imp.gpid = gpid; + } + return imp; } diff --git a/test/spec/modules/optidigitalBidAdapter_spec.js b/test/spec/modules/optidigitalBidAdapter_spec.js index 3b4ef61e961..bb0526d9aaa 100755 --- a/test/spec/modules/optidigitalBidAdapter_spec.js +++ b/test/spec/modules/optidigitalBidAdapter_spec.js @@ -63,8 +63,7 @@ describe('optidigitalAdapterTests', function () { 'adserver': { 'name': 'gam', 'adslot': '/19968336/header-bid-tag-0' - }, - 'pbadslot': '/19968336/header-bid-tag-0' + } }, 'gpid': '/19968336/header-bid-tag-0' } @@ -212,7 +211,8 @@ describe('optidigitalAdapterTests', function () { it('should send bid request to given endpoint', function() { const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.url).to.equal(ENDPOINT); + const finalEndpoint = `${ENDPOINT}/s123`; + expect(request.url).to.equal(finalEndpoint); }); it('should be bidRequest data', function () { @@ -282,6 +282,49 @@ describe('optidigitalAdapterTests', function () { expect(payload.imp[0].adContainerHeight).to.exist; }); + it('should read container size from DOM when divId exists', function () { + const containerId = 'od-test-container'; + const el = document.createElement('div'); + el.id = containerId; + el.style.width = '321px'; + el.style.height = '111px'; + el.style.position = 'absolute'; + el.style.left = '0'; + el.style.top = '0'; + document.body.appendChild(el); + + const validBidRequestsWithDivId = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'divId': containerId + }, + 'adUnitCode': containerId, + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ]; + + const request = spec.buildRequests(validBidRequestsWithDivId, bidderRequest); + const payload = JSON.parse(request.data); + try { + expect(payload.imp[0].adContainerWidth).to.equal(el.offsetWidth); + expect(payload.imp[0].adContainerHeight).to.equal(el.offsetHeight); + } finally { + document.body.removeChild(el); + } + }); + it('should add pageTemplate to payload if pageTemplate exsists in parameter', function () { const validBidRequestsWithDivId = [ { @@ -325,7 +368,8 @@ describe('optidigitalAdapterTests', function () { 'domain': 'example.com', 'publisher': { 'domain': 'example.com' - } + }, + 'keywords': 'key1,key2' }, 'device': { 'w': 1507, @@ -386,6 +430,12 @@ describe('optidigitalAdapterTests', function () { expect(payload.bapp).to.deep.equal(validBidRequests[0].params.bapp); }); + it('should add keywords to payload when site keywords present', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.site.keywords).to.deep.equal('key1,key2'); + }); + it('should send empty GDPR consent and required set to false', function() { const request = spec.buildRequests(validBidRequests, bidderRequest); const payload = JSON.parse(request.data); @@ -401,6 +451,7 @@ describe('optidigitalAdapterTests', function () { 'vendorData': { 'hasGlobalConsent': false }, + 'addtlConsent': '1~7.12.35.62.66.70.89.93.108', 'apiVersion': 1 } const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -408,6 +459,7 @@ describe('optidigitalAdapterTests', function () { expect(payload.gdpr).to.exist; expect(payload.gdpr.consent).to.equal(consentString); expect(payload.gdpr.required).to.exist.and.to.be.true; + expect(payload.gdpr.addtlConsent).to.exist; }); it('should send empty GDPR consent to endpoint', function() { @@ -425,6 +477,22 @@ describe('optidigitalAdapterTests', function () { expect(payload.gdpr.consent).to.equal(''); }); + it('should send GDPR consent and required set to false when gdprApplies is not boolean', function() { + let consentString = 'DFR8KRePoQNsRREZCADBG+A=='; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': "", + 'vendorData': { + 'hasGlobalConsent': false + }, + 'apiVersion': 1 + } + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr.consent).to.equal(consentString); + expect(payload.gdpr.required).to.exist.and.to.be.false; + }); + it('should send uspConsent to given endpoint', function() { bidderRequest.uspConsent = '1YYY'; const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -457,6 +525,21 @@ describe('optidigitalAdapterTests', function () { expect(payload.gpp).to.exist; }); + it('should set testMode when optidigitalTestMode flag present in location', function() { + const originalUrl = window.location.href; + const newUrl = originalUrl.includes('optidigitalTestMode=true') + ? originalUrl + : `${window.location.pathname}${window.location.search}${window.location.search && window.location.search.length ? '&' : '?'}optidigitalTestMode=true${window.location.hash || ''}`; + try { + window.history.pushState({}, '', newUrl); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.testMode).to.equal(true); + } finally { + window.history.replaceState({}, '', originalUrl); + } + }); + it('should use appropriate mediaTypes banner sizes', function() { const mediaTypesBannerSize = { 'mediaTypes': { @@ -532,6 +615,23 @@ describe('optidigitalAdapterTests', function () { expect(payload.user).to.deep.equal(undefined); }); + it('should add gpid to payload when gpid', function() { + validBidRequests[0].ortb2Imp = { + 'ext': { + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/19968336/header-bid-tag-0' + } + }, + 'gpid': '/19968336/header-bid-tag-0' + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].gpid).to.deep.equal('/19968336/header-bid-tag-0'); + }); + function returnBannerSizes(mediaTypes, expectedSizes) { const bidRequest = Object.assign(validBidRequests[0], mediaTypes); const request = spec.buildRequests([bidRequest], bidderRequest); From 1c9900031b42b10f3b6e2fe835efa842edc5daf7 Mon Sep 17 00:00:00 2001 From: Brian Weiss Date: Tue, 10 Feb 2026 09:07:35 -0600 Subject: [PATCH 031/105] LocID User ID Submodule: add locIdSystem (#14367) * chore(prebid): scaffold locid user id module * update LocID module to support first-party endpoint fetching and privacy signal handling * update LocID system tests for gdpr handling and consent string validation * Enhance LocID module documentation and tests for privacy signal handling. Updated comments for clarity, added test cases for maximum ID length and empty endpoint handling, and refined privacy configuration notes in the documentation. * Refactor LocID module to standardize naming conventions and enhance privacy signal handling. Updated module name to 'locId', improved consent data processing functions, and revised documentation and tests accordingly. * Added Apache 2.0 license header. * Add LocID User ID sub-module documentation and refactor ajax usage in locIdSystem module - Introduced locid.md documentation detailing the LocID User ID sub-module, including installation, configuration, parameters, and privacy handling. - Refactored locIdSystem.js to utilize ajaxBuilder for improved request handling. - Updated tests in locIdSystem_spec.js to accommodate changes in ajax usage and ensure proper timeout configurations. * Remove docs folder - to be submitted to prebid.github.io separately * Update LocID atype to 1 for compliance with OpenRTB 2.6 specifications - Changed the default EID atype in locIdSystem.js and related documentation from 3384 to 1, reflecting the correct device identifier as per OpenRTB 2.6 Extended Identifiers spec. - Updated references in locIdSystem.md and userId/eids.md to ensure consistency across documentation. - Adjusted unit tests in locIdSystem_spec.js to validate the new atype value. * Enhance LocID module with connection IP handling and response parsing improvements - Introduced connection IP validation and storage alongside LocID to support IP-aware caching. - Updated response parsing to only extract `tx_cloc` and `connection_ip`, ignoring `stable_cloc`. - Enhanced documentation to reflect changes in stored value format and endpoint response requirements. - Modified unit tests to cover new functionality, including connection IP checks and expiration handling. * fix getValue string handling, GDPR enforcement gating, extendId docs * LocID: enforce IP cache TTL in extendId and update tests/docs * LocID: honor null tx_cloc, reject whitespace-only IDs, add stable_cloc exclusion test * LocID: remove legacy 3384 references and enforce atype 1 * LocID: add vendorless TCF marker and scope 3384 guard * enhance locIdSystem to handle whitespace-only tx_cloc and update documentation. Ensure null IDs are cached correctly when tx_cloc is empty or whitespace, and adjust caching logic to honor null responses from the main endpoint. --------- Co-authored-by: Brian --- modules/.submodules.json | 3 +- modules/locIdSystem.js | 676 ++++++++ modules/locIdSystem.md | 250 +++ modules/userId/eids.md | 8 + modules/userId/userId.md | 10 + test/build-logic/no_3384_spec.mjs | 47 + test/spec/modules/locIdSystem_spec.js | 2090 +++++++++++++++++++++++++ 7 files changed, 3083 insertions(+), 1 deletion(-) create mode 100644 modules/locIdSystem.js create mode 100644 modules/locIdSystem.md create mode 100644 test/build-logic/no_3384_spec.mjs create mode 100644 test/spec/modules/locIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index c6274fdcbfd..d87b73401cd 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -32,6 +32,7 @@ "kinessoIdSystem", "liveIntentIdSystem", "lmpIdSystem", + "locIdSystem", "lockrAIMIdSystem", "lotamePanoramaIdSystem", "merkleIdSystem", @@ -147,4 +148,4 @@ "topLevelPaapi" ] } -} \ No newline at end of file +} diff --git a/modules/locIdSystem.js b/modules/locIdSystem.js new file mode 100644 index 00000000000..5e06e29b302 --- /dev/null +++ b/modules/locIdSystem.js @@ -0,0 +1,676 @@ +/** + * This file is licensed under the Apache 2.0 license. + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * This module adds LocID to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/locIdSystem + * @requires module:modules/userId + */ + +import { logWarn, logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { gppDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { VENDORLESS_GVLID } from '../src/consentHandler.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; + +const MODULE_NAME = 'locId'; +const LOG_PREFIX = 'LocID:'; +const DEFAULT_TIMEOUT_MS = 800; +const DEFAULT_EID_SOURCE = 'locid.com'; +// EID atype: 1 = AdCOM AgentTypeWeb (agent type for web environments) +const DEFAULT_EID_ATYPE = 1; +const MAX_ID_LENGTH = 512; +const MAX_CONNECTION_IP_LENGTH = 64; +const DEFAULT_IP_CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours +const IP_CACHE_SUFFIX = '_ip'; + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +/** + * Normalizes privacy mode config to a boolean flag. + * Supports both requirePrivacySignals (boolean) and privacyMode (string enum). + * + * Precedence: requirePrivacySignals (if true) takes priority over privacyMode. + * - privacyMode is the preferred high-level setting for new integrations. + * - requirePrivacySignals exists for backwards compatibility with integrators + * who prefer a simple boolean. + * + * @param {Object} params - config.params + * @returns {boolean} true if privacy signals are required, false otherwise + */ +function shouldRequirePrivacySignals(params) { + // requirePrivacySignals=true takes precedence (backwards compatibility) + if (params?.requirePrivacySignals === true) { + return true; + } + if (params?.privacyMode === 'requireSignals') { + return true; + } + // Default: allowWithoutSignals + return false; +} + +function getUspConsent(consentData) { + if (consentData && consentData.usp != null) { + return consentData.usp; + } + return consentData?.uspConsent; +} + +function getGppConsent(consentData) { + if (consentData && consentData.gpp != null) { + return consentData.gpp; + } + return consentData?.gppConsent; +} + +/** + * Checks if any privacy signals are present in consentData or data handlers. + * + * IMPORTANT: gdprApplies alone does NOT count as a privacy signal. + * A publisher may set gdprApplies=true without having a CMP installed. + * We only consider GDPR signals "present" when actual consent framework + * artifacts exist (consentString, vendorData). This supports LI-based + * operation where no TCF consent string is required. + * + * "Signals present" means ANY of the following are available: + * - consentString or gdpr.consentString (indicates CMP provided framework data) + * - vendorData or gdpr.vendorData (indicates CMP provided vendor data) + * - usp or uspConsent (US Privacy string) + * - gpp or gppConsent (GPP consent data) + * - Data from gppDataHandler or uspDataHandler + * + * @param {Object} consentData - The consent data object passed to getId + * @returns {boolean} true if any privacy signals are present + */ +function hasPrivacySignals(consentData) { + // Check GDPR-related signals (flat and nested) + // NOTE: gdprApplies alone is NOT a signal - it just indicates jurisdiction. + // A signal requires actual CMP artifacts (consentString or vendorData). + if (consentData?.consentString || consentData?.gdpr?.consentString) { + return true; + } + if (consentData?.vendorData || consentData?.gdpr?.vendorData) { + return true; + } + + // Check USP consent + const uspConsent = getUspConsent(consentData); + if (uspConsent) { + return true; + } + + // Check GPP consent + const gppConsent = getGppConsent(consentData); + if (gppConsent) { + return true; + } + + // Check data handlers + const uspFromHandler = uspDataHandler.getConsentData(); + if (uspFromHandler) { + return true; + } + + const gppFromHandler = gppDataHandler.getConsentData(); + if (gppFromHandler) { + return true; + } + + return false; +} + +function isValidId(id) { + return typeof id === 'string' && id.trim().length > 0 && id.length <= MAX_ID_LENGTH; +} + +function isValidConnectionIp(ip) { + return typeof ip === 'string' && ip.length > 0 && ip.length <= MAX_CONNECTION_IP_LENGTH; +} + +function normalizeStoredId(storedId) { + if (!storedId) { + return null; + } + if (typeof storedId === 'string') { + return null; + } + if (typeof storedId === 'object') { + // Preserve explicit null for id (means "empty tx_cloc, valid cached response"). + // 'id' in storedId is needed because ?? treats null as nullish and would + // incorrectly fall through to tx_cloc. + const hasExplicitId = 'id' in storedId; + const id = hasExplicitId ? storedId.id : (storedId.tx_cloc ?? null); + const connectionIp = storedId.connectionIp ?? storedId.connection_ip; + return { ...storedId, id, connectionIp }; + } + return null; +} + +/** + * Checks privacy framework signals. Returns true if ID operations are allowed. + * + * LocID operates under Legitimate Interest and does not require a TCF consent + * string when no privacy framework is present. When privacy signals exist, + * framework processing restrictions are enforced. + * + * @param {Object} consentData - The consent data object from Prebid + * @param {Object} params - config.params for privacy mode settings + * @returns {boolean} true if ID operations are allowed + */ +function hasValidConsent(consentData, params) { + const requireSignals = shouldRequirePrivacySignals(params); + const signalsPresent = hasPrivacySignals(consentData); + + // B) If privacy signals are NOT present + if (!signalsPresent) { + if (requireSignals) { + logWarn(LOG_PREFIX, 'Privacy signals required but none present'); + return false; + } + // Default: allow operation without privacy signals (LI-based operation) + return true; + } + + // A) Privacy signals ARE present - enforce applicable restrictions + // + // Note: privacy signals can come from GDPR, USP, or GPP. GDPR checks only + // apply when GDPR is flagged AND CMP artifacts (consentString/vendorData) + // are present. gdprApplies alone does not trigger GDPR enforcement. + + // Check GDPR - support both flat and nested shapes + const gdprApplies = consentData?.gdprApplies === true || consentData?.gdpr?.gdprApplies === true; + const consentString = consentData?.consentString || consentData?.gdpr?.consentString; + const vendorData = consentData?.vendorData || consentData?.gdpr?.vendorData; + const gdprCmpArtifactsPresent = !!(consentString || vendorData); + + if (gdprApplies && gdprCmpArtifactsPresent) { + // When GDPR applies AND we have CMP signals, require consentString + if (!consentString || consentString.length === 0) { + logWarn(LOG_PREFIX, 'GDPR framework data missing consent string'); + return false; + } + } + + // Check USP for processing restriction + const uspData = getUspConsent(consentData) ?? uspDataHandler.getConsentData(); + if (uspData && uspData.length >= 3 && uspData.charAt(2) === 'Y') { + logWarn(LOG_PREFIX, 'US Privacy framework processing restriction detected'); + return false; + } + + // Check GPP for processing restriction + const gppData = getGppConsent(consentData) ?? gppDataHandler.getConsentData(); + if (gppData?.applicableSections?.includes(7) && + gppData?.parsedSections?.usnat?.KnownChildSensitiveDataConsents?.includes(1)) { + logWarn(LOG_PREFIX, 'GPP usnat KnownChildSensitiveDataConsents processing restriction detected'); + return false; + } + + return true; +} + +function parseEndpointResponse(response) { + if (!response) { + return null; + } + try { + return typeof response === 'string' ? JSON.parse(response) : response; + } catch (e) { + logError(LOG_PREFIX, 'Error parsing endpoint response:', e.message); + return null; + } +} + +/** + * Extracts LocID from endpoint response. + * Only tx_cloc is accepted. + */ +function extractLocIdFromResponse(parsed) { + if (!parsed) return null; + + if (isValidId(parsed.tx_cloc)) { + return parsed.tx_cloc; + } + + logWarn(LOG_PREFIX, 'Could not extract valid tx_cloc from response'); + return null; +} + +function extractConnectionIp(parsed) { + if (!parsed) { + return null; + } + const connectionIp = parsed.connection_ip ?? parsed.connectionIp; + return isValidConnectionIp(connectionIp) ? connectionIp : null; +} + +function getIpCacheKey(config) { + const baseName = config?.storage?.name || '_locid'; + return config?.params?.ipCacheName || (baseName + IP_CACHE_SUFFIX); +} + +function getIpCacheTtlMs(config) { + const ttl = config?.params?.ipCacheTtlMs; + return (typeof ttl === 'number' && ttl > 0) ? ttl : DEFAULT_IP_CACHE_TTL_MS; +} + +function readIpCache(config) { + try { + const key = getIpCacheKey(config); + const raw = storage.getDataFromLocalStorage(key); + if (!raw) return null; + const entry = JSON.parse(raw); + if (!entry || typeof entry !== 'object') return null; + if (!isValidConnectionIp(entry.ip)) return null; + if (typeof entry.expiresAt === 'number' && Date.now() > entry.expiresAt) return null; + return entry; + } catch (e) { + logWarn(LOG_PREFIX, 'Error reading IP cache:', e.message); + return null; + } +} + +function writeIpCache(config, ip) { + if (!isValidConnectionIp(ip)) return; + try { + const key = getIpCacheKey(config); + const nowMs = Date.now(); + const ttlMs = getIpCacheTtlMs(config); + const entry = { + ip: ip, + fetchedAt: nowMs, + expiresAt: nowMs + ttlMs + }; + storage.setDataInLocalStorage(key, JSON.stringify(entry)); + } catch (e) { + logWarn(LOG_PREFIX, 'Error writing IP cache:', e.message); + } +} + +/** + * Parses an IP response from an IP-only endpoint. + * Supports JSON ({ip: "..."}, {connection_ip: "..."}) and plain text IP. + */ +function parseIpResponse(response) { + if (!response) return null; + + if (typeof response === 'string') { + const trimmed = response.trim(); + if (trimmed.charAt(0) === '{') { + try { + const parsed = JSON.parse(trimmed); + const ip = parsed.ip || parsed.connection_ip || parsed.connectionIp; + return isValidConnectionIp(ip) ? ip : null; + } catch (e) { + // Not valid JSON, try as plain text + } + } + return isValidConnectionIp(trimmed) ? trimmed : null; + } + + if (typeof response === 'object') { + const ip = response.ip || response.connection_ip || response.connectionIp; + return isValidConnectionIp(ip) ? ip : null; + } + + return null; +} + +/** + * Checks if a stored tx_cloc entry is valid for reuse. + * Accepts both valid id strings AND null (empty tx_cloc is a valid cached result). + */ +function isStoredEntryReusable(normalizedStored, currentIp) { + if (!normalizedStored || !isValidConnectionIp(normalizedStored.connectionIp)) { + return false; + } + if (isExpired(normalizedStored)) { + return false; + } + if (currentIp && normalizedStored.connectionIp !== currentIp) { + return false; + } + // id must be either a valid string or explicitly null (empty tx_cloc) + return normalizedStored.id === null || isValidId(normalizedStored.id); +} + +function getExpiresAt(config, nowMs) { + const expiresDays = config?.storage?.expires; + if (typeof expiresDays !== 'number' || expiresDays <= 0) { + return undefined; + } + return nowMs + (expiresDays * 24 * 60 * 60 * 1000); +} + +function buildStoredId(id, connectionIp, config) { + const nowMs = Date.now(); + return { + id, + connectionIp, + createdAt: nowMs, + updatedAt: nowMs, + expiresAt: getExpiresAt(config, nowMs) + }; +} + +function isExpired(storedEntry) { + return typeof storedEntry?.expiresAt === 'number' && Date.now() > storedEntry.expiresAt; +} + +/** + * Builds the request URL, appending altId if configured. + * Preserves URL fragments by appending query params before the hash. + */ +function buildRequestUrl(endpoint, altId) { + if (!altId) { + return endpoint; + } + + // Split on hash to preserve fragment + const hashIndex = endpoint.indexOf('#'); + let base = endpoint; + let fragment = ''; + + if (hashIndex !== -1) { + base = endpoint.substring(0, hashIndex); + fragment = endpoint.substring(hashIndex); + } + + const separator = base.includes('?') ? '&' : '?'; + return `${base}${separator}alt_id=${encodeURIComponent(altId)}${fragment}`; +} + +/** + * Fetches LocID from the configured endpoint (GET only). + */ +function fetchLocIdFromEndpoint(config, callback) { + const params = config?.params || {}; + const endpoint = params.endpoint; + const timeoutMs = params.timeoutMs || DEFAULT_TIMEOUT_MS; + + if (!endpoint) { + logError(LOG_PREFIX, 'No endpoint configured'); + callback(undefined); + return; + } + + const requestUrl = buildRequestUrl(endpoint, params.altId); + + const requestOptions = { + method: 'GET', + contentType: 'application/json', + withCredentials: params.withCredentials === true + }; + + // Add x-api-key header if apiKey is configured + if (params.apiKey) { + requestOptions.customHeaders = { + 'x-api-key': params.apiKey + }; + } + + let callbackFired = false; + const safeCallback = (result) => { + if (!callbackFired) { + callbackFired = true; + callback(result); + } + }; + + const onSuccess = (response) => { + const parsed = parseEndpointResponse(response); + if (!parsed) { + safeCallback(undefined); + return; + } + const connectionIp = extractConnectionIp(parsed); + if (!connectionIp) { + logWarn(LOG_PREFIX, 'Missing or invalid connection_ip in response'); + safeCallback(undefined); + return; + } + // tx_cloc may be null (empty/missing for this IP) -- this is a valid cacheable result. + // connection_ip is always required. + const locId = extractLocIdFromResponse(parsed); + writeIpCache(config, connectionIp); + safeCallback(buildStoredId(locId, connectionIp, config)); + }; + + const onError = (error) => { + logWarn(LOG_PREFIX, 'Request failed:', error); + safeCallback(undefined); + }; + + try { + const ajax = ajaxBuilder(timeoutMs); + ajax(requestUrl, { success: onSuccess, error: onError }, null, requestOptions); + } catch (e) { + logError(LOG_PREFIX, 'Error initiating request:', e.message); + safeCallback(undefined); + } +} + +/** + * Fetches the connection IP from a separate lightweight endpoint (GET only). + * Callback receives the IP string on success or null on failure. + */ +function fetchIpFromEndpoint(config, callback) { + const params = config?.params || {}; + const ipEndpoint = params.ipEndpoint; + const timeoutMs = params.timeoutMs || DEFAULT_TIMEOUT_MS; + + if (!ipEndpoint) { + callback(null); + return; + } + + let callbackFired = false; + const safeCallback = (result) => { + if (!callbackFired) { + callbackFired = true; + callback(result); + } + }; + + const onSuccess = (response) => { + const ip = parseIpResponse(response); + safeCallback(ip); + }; + + const onError = (error) => { + logWarn(LOG_PREFIX, 'IP endpoint request failed:', error); + safeCallback(null); + }; + + try { + const ajax = ajaxBuilder(timeoutMs); + const requestOptions = { + method: 'GET', + withCredentials: params.withCredentials === true + }; + if (params.apiKey) { + requestOptions.customHeaders = { + 'x-api-key': params.apiKey + }; + } + ajax(ipEndpoint, { success: onSuccess, error: onError }, null, requestOptions); + } catch (e) { + logError(LOG_PREFIX, 'Error initiating IP request:', e.message); + safeCallback(null); + } +} + +export const locIdSubmodule = { + name: MODULE_NAME, + aliasName: 'locid', + gvlid: VENDORLESS_GVLID, + + /** + * Decode stored value into userId object. + */ + decode(value) { + if (!value || typeof value !== 'object') { + return undefined; + } + const id = value?.id ?? value?.tx_cloc; + const connectionIp = value?.connectionIp ?? value?.connection_ip; + if (isValidId(id) && isValidConnectionIp(connectionIp)) { + return { locId: id }; + } + return undefined; + }, + + /** + * Get the LocID from endpoint. + * Returns {id} for sync or {callback} for async per Prebid patterns. + * + * Two-tier cache: IP cache (4h default) and tx_cloc cache (7d default). + * IP is refreshed more frequently to detect network changes while keeping + * tx_cloc stable for its full cache period. + */ + getId(config, consentData, storedId) { + const params = config?.params || {}; + + // Check privacy restrictions first + if (!hasValidConsent(consentData, params)) { + return undefined; + } + + const normalizedStored = normalizeStoredId(storedId); + const cachedIp = readIpCache(config); + + // Step 1: IP cache is valid -- check if tx_cloc matches + if (cachedIp) { + if (isStoredEntryReusable(normalizedStored, cachedIp.ip)) { + return { id: normalizedStored }; + } + // IP cached but tx_cloc missing, expired, or IP mismatch -- full fetch + return { + callback: (callback) => { + fetchLocIdFromEndpoint(config, callback); + } + }; + } + + // Step 2: IP cache expired or missing + if (params.ipEndpoint) { + // Two-call optimization: lightweight IP check first + return { + callback: (callback) => { + fetchIpFromEndpoint(config, (freshIp) => { + if (!freshIp) { + // IP fetch failed; fall back to main endpoint + fetchLocIdFromEndpoint(config, callback); + return; + } + writeIpCache(config, freshIp); + // Check if stored tx_cloc matches the fresh IP + if (isStoredEntryReusable(normalizedStored, freshIp)) { + callback(normalizedStored); + return; + } + // IP changed or no valid tx_cloc -- full fetch + fetchLocIdFromEndpoint(config, callback); + }); + } + }; + } + + // Step 3: No ipEndpoint configured -- call main endpoint to refresh IP. + // Only update tx_cloc if IP changed or tx_cloc cache expired. + return { + callback: (callback) => { + fetchLocIdFromEndpoint(config, (freshEntry) => { + if (!freshEntry) { + callback(undefined); + return; + } + // Honor empty tx_cloc: if the server returned null, use the fresh + // entry so stale identifiers are cleared (cached as id: null). + if (freshEntry.id === null) { + callback(freshEntry); + return; + } + // IP is already cached by fetchLocIdFromEndpoint's onSuccess. + // Check if we should preserve the existing tx_cloc (avoid churning it). + if (normalizedStored?.id !== null && isStoredEntryReusable(normalizedStored, freshEntry.connectionIp)) { + callback(normalizedStored); + return; + } + // IP changed or tx_cloc expired/missing -- use fresh entry + callback(freshEntry); + }); + } + }; + }, + + /** + * Extend existing LocID. + * Accepts id: null (empty tx_cloc) as a valid cached result. + * If IP cache is missing/expired/mismatched, return a callback to refresh. + */ + extendId(config, consentData, storedId) { + const normalizedStored = normalizeStoredId(storedId); + if (!normalizedStored || !isValidConnectionIp(normalizedStored.connectionIp)) { + return undefined; + } + // Accept both valid id strings AND null (empty tx_cloc is a valid cached result) + if (normalizedStored.id !== null && !isValidId(normalizedStored.id)) { + return undefined; + } + if (isExpired(normalizedStored)) { + return undefined; + } + if (!hasValidConsent(consentData, config?.params)) { + return undefined; + } + const refreshInSeconds = config?.storage?.refreshInSeconds; + if (typeof refreshInSeconds === 'number' && refreshInSeconds > 0) { + const createdAt = normalizedStored.createdAt; + if (typeof createdAt !== 'number') { + return undefined; + } + const refreshAfterMs = refreshInSeconds * 1000; + if (Date.now() - createdAt >= refreshAfterMs) { + return undefined; + } + } + // Check IP cache -- if expired/missing or IP changed, trigger re-fetch + const cachedIp = readIpCache(config); + if (!cachedIp || cachedIp.ip !== normalizedStored.connectionIp) { + return { + callback: (callback) => { + fetchLocIdFromEndpoint(config, callback); + } + }; + } + return { id: normalizedStored }; + }, + + /** + * EID configuration following standard Prebid shape. + */ + eids: { + locId: { + source: DEFAULT_EID_SOURCE, + atype: DEFAULT_EID_ATYPE, + getValue: function (data) { + if (typeof data === 'string') { + return data; + } + if (!data || typeof data !== 'object') { + return undefined; + } + return data?.id ?? data?.tx_cloc ?? data?.locId ?? data?.locid; + } + } + } +}; + +submodule('userId', locIdSubmodule); diff --git a/modules/locIdSystem.md b/modules/locIdSystem.md new file mode 100644 index 00000000000..6ffe02fe841 --- /dev/null +++ b/modules/locIdSystem.md @@ -0,0 +1,250 @@ +# LocID User ID Submodule + +## Overview + +The LocID User ID submodule retrieves a LocID from a configured first-party endpoint, honors applicable privacy framework processing restrictions when present, persists the identifier using Prebid's storage framework, and exposes the ID to bidders via the standard EIDs interface. + +LocID is a geospatial identifier provided by Digital Envoy. The endpoint is a publisher-controlled, first-party or on-premises service operated by the publisher, GrowthCode, or Digital Envoy. The endpoint derives location information server-side. The browser module does not transmit IP addresses. + +## Configuration + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'locId', + params: { + endpoint: 'https://id.example.com/locid', + ipEndpoint: 'https://id.example.com/ip' // optional: lightweight IP-only check + }, + storage: { + type: 'html5', + name: '_locid', + expires: 7 + } + }] + } +}); +``` + +## Parameters + +| Parameter | Type | Required | Default | Description | +| ----------------------- | ------- | -------- | ----------------------- | ----------------------------------------------------------------------------- | +| `endpoint` | String | Yes | – | First-party LocID endpoint (see Endpoint Requirements below) | +| `ipEndpoint` | String | No | – | Separate endpoint returning the connection IP (see IP Change Detection below) | +| `ipCacheTtlMs` | Number | No | `14400000` (4h) | TTL for the IP cache entry in milliseconds | +| `ipCacheName` | String | No | `{storage.name}_ip` | localStorage key for the IP cache (auto-derived if not set) | +| `altId` | String | No | – | Alternative identifier appended as `?alt_id=` query param | +| `timeoutMs` | Number | No | `800` | Request timeout in milliseconds | +| `withCredentials` | Boolean | No | `false` | Whether to include credentials on the request | +| `apiKey` | String | No | – | API key sent as `x-api-key` header on `endpoint` and `ipEndpoint` requests | +| `requirePrivacySignals` | Boolean | No | `false` | If `true`, requires privacy signals to be present | +| `privacyMode` | String | No | `'allowWithoutSignals'` | `'allowWithoutSignals'` or `'requireSignals'` | + +**Note on privacy configuration:** `privacyMode` is the preferred high-level setting for new integrations. `requirePrivacySignals` exists for backwards compatibility with integrators who prefer a simple boolean. If `requirePrivacySignals: true` is set, it takes precedence. + +### Endpoint Requirements + +The `endpoint` parameter must point to a **first-party proxy** or **on-premises service**—not the raw LocID Encrypt API directly. + +The LocID Encrypt API (`GET /encrypt?ip=&alt_id=`) requires the client IP address as a parameter. Since browsers cannot reliably determine their own public IP, a server-side proxy is required to: + +1. Receive the request from the browser +2. Extract the client IP from the incoming connection +3. Forward the request to the LocID Encrypt API with the IP injected +4. Return the response with `tx_cloc` and `connection_ip` to the browser (any `stable_cloc` is ignored client-side) + +This architecture ensures the browser never transmits IP addresses and the LocID service receives accurate location data. + +If you configure `altId`, the module appends it as `?alt_id=` to the endpoint URL. Your proxy can then forward this to the LocID API. + +### CORS Configuration + +If your endpoint is on a different origin or you set `withCredentials: true`, ensure your server returns appropriate CORS headers: + +```http +Access-Control-Allow-Origin: +Access-Control-Allow-Credentials: true +``` + +When using `withCredentials`, the server cannot use `Access-Control-Allow-Origin: *`; it must specify the exact origin. + +### Storage Configuration + +| Parameter | Required | Description | +| --------- | -------- | ---------------- | +| `type` | Yes | `'html5'` | +| `name` | Yes | Storage key name | +| `expires` | No | TTL in days | + +### Stored Value Format + +The module stores a structured object (rather than a raw string) so it can track IP-aware metadata: + +```json +{ + "id": "", + "connectionIp": "203.0.113.42", + "createdAt": 1738147200000, + "updatedAt": 1738147200000, + "expiresAt": 1738752000000 +} +``` + +When the endpoint returns a valid `connection_ip` but no usable `tx_cloc` (`null`, missing, empty, or whitespace-only), `id` is stored as `null`. This caches the "no location for this IP" result for the full cache period without re-fetching. The `decode()` function returns `undefined` for `null` IDs, so no EID is emitted in bid requests. + +**Important:** String-only stored values are treated as invalid and are not emitted. + +### IP Cache Format + +The module maintains a separate IP cache entry in localStorage (default key: `{storage.name}_ip`) with a shorter TTL (default 4 hours): + +```json +{ + "ip": "203.0.113.42", + "fetchedAt": 1738147200000, + "expiresAt": 1738161600000 +} +``` + +This entry is managed by the module directly via Prebid's `storageManager` and is independent of the framework-managed tx_cloc cache. + +## Operation Flow + +The module uses a two-tier cache: an IP cache (default 4-hour TTL) and a tx_cloc cache (TTL defined by `storage.expires`). The IP is refreshed more frequently to detect network changes while keeping tx_cloc stable for its full cache period. + +1. The module checks the IP cache for a current connection IP. +2. If the IP cache is valid, the module compares it against the stored tx_cloc entry's `connectionIp`. +3. If the IPs match and the tx_cloc entry is not expired, the cached tx_cloc is reused (even if `null`). +4. If the IP cache is expired or missing and `ipEndpoint` is configured, the module calls `ipEndpoint` to get the current IP, then compares with the stored tx_cloc. If the IPs match, the tx_cloc is reused without calling the main endpoint. +5. If the IPs differ, or the tx_cloc is expired/missing, or `ipEndpoint` is not configured, the module calls the main endpoint to get a fresh tx_cloc and connection IP. +6. The endpoint response may include a `null`, empty, whitespace-only, or missing `tx_cloc` (indicating no location for this IP). This is cached as `id: null` for the full cache period, and overrides any previously stored non-null ID for that same IP. +7. Both the IP cache and tx_cloc cache are updated after each endpoint call. +8. The ID is included in bid requests via the EIDs array. Entries with `null` tx_cloc are omitted from bid requests. + +## Endpoint Response Requirements + +The proxy must return: + +```json +{ + "tx_cloc": "", + "connection_ip": "203.0.113.42" +} +``` + +Notes: + +- `connection_ip` is always required. If missing, the entire response is treated as a failure. +- `tx_cloc` may be `null`, missing, empty, or whitespace-only when no location is available for the IP. This is a valid response and will be cached as `id: null` for the configured cache period. +- `tx_cloc` is the only value the browser module will store/transmit. +- `stable_cloc` may exist in proxy responses for server-side caching, but the client ignores it. + +## IP Change Detection + +The module uses a two-tier cache to detect IP changes without churning the tx_cloc identifier: + +- **IP cache** (default 4-hour TTL): Tracks the current connection IP. Stored in a separate localStorage key (`{storage.name}_ip`). +- **tx_cloc cache** (`storage.expires`): Stores the LocID. Managed by Prebid's userId framework. + +When the IP cache expires, the module refreshes the IP. If the IP is unchanged and the tx_cloc cache is still valid, the existing tx_cloc is reused without calling the main endpoint. + +### ipEndpoint (optional) + +When `ipEndpoint` is configured, the module calls it for lightweight IP-only checks. This avoids a full tx_cloc API call when only the IP needs refreshing. The endpoint should return the connection IP in one of these formats: + +- JSON: `{"ip": "203.0.113.42"}` or `{"connection_ip": "203.0.113.42"}` +- Plain text: `203.0.113.42` + +If `apiKey` is configured, the `x-api-key` header is included on `ipEndpoint` requests using the same `customHeaders` mechanism as the main endpoint. + +When `ipEndpoint` is not configured, the module falls back to calling the main endpoint to refresh the IP, but only updates the stored tx_cloc when the IP has changed or the tx_cloc cache has expired. In this mode, IP changes are only detected when the IP cache is refreshed (for example when it expires and `extendId()` returns a refresh callback); there is no separate lightweight proactive IP probe. + +### Prebid Refresh Triggers + +When `storage.refreshInSeconds` is set, the module will reuse the cached ID until `createdAt + refreshInSeconds`; once due (or if `createdAt` is missing), `extendId()` returns `undefined` to indicate the cached ID should not be reused. The `extendId()` method also checks the IP cache: if the IP cache is expired or missing, or if the cached IP differs from the stored tx_cloc's IP, it returns a callback that refreshes via the main endpoint. This enforces the IP cache TTL (`ipCacheTtlMs`) even when the tx_cloc cache has not expired. + +## Consent Handling + +LocID operates under Legitimate Interest (LI). By default, the module proceeds when no privacy framework signals are present. When privacy signals exist, they are enforced. Privacy frameworks can only stop LocID via global processing restrictions; they do not enable it. +For TCF integration, the module declares Prebid's vendorless GVL marker so purpose-level enforcement applies without vendor-ID checks. + +### Legal Basis and IP-Based Identifiers + +LocID is derived from IP-based geolocation. Because IP addresses are transient and shared, there is no meaningful IP-level choice to express. Privacy frameworks are only consulted to honor rare, publisher- or regulator-level instructions to stop all processing. When such a global processing restriction is signaled, LocID respects it by returning `undefined`. + +### Default Behavior (allowWithoutSignals) + +- **No privacy signals present**: Module proceeds and fetches the ID +- **Privacy signals present**: Enforcement rules apply (see below) + +### Strict Mode (requireSignals) + +Set `requirePrivacySignals: true` or `privacyMode: 'requireSignals'` to require privacy signals: + +```javascript +params: { + endpoint: 'https://id.example.com/locid', + requirePrivacySignals: true +} +``` + +In strict mode, the module returns `undefined` if no privacy signals are present. + +### Privacy Signal Enforcement + +When privacy signals **are** present, the module does not fetch or return an ID if any of the following apply: + +- GDPR applies and vendorData is present, but consentString is missing or empty +- The US Privacy string indicates a global processing restriction (third character is 'Y') +- GPP signals indicate an applicable processing restriction + +When GDPR applies and `consentString` is present, the module proceeds unless a framework processing restriction is signaled. + +### Privacy Signals Detection + +The module considers privacy signals "present" if any of the following exist: + +- `consentString` (TCF consent string from CMP) +- `vendorData` (TCF vendor data from CMP) +- `usp` or `uspConsent` (US Privacy string) +- `gpp` or `gppConsent` (GPP consent data) +- Data from `uspDataHandler` or `gppDataHandler` + +**Important:** `gdprApplies` alone does NOT constitute a privacy signal. A publisher may indicate GDPR jurisdiction without having a CMP installed. TCF framework data is only required when actual CMP artifacts (`consentString` or `vendorData`) are present. This supports Legitimate Interest-based operation in deployments without a full TCF implementation. + +## EID Output + +When available, the LocID is exposed as: + +```javascript +{ + source: "locid.com", + uids: [{ + id: "", + atype: 1 // AdCOM AgentTypeWeb + }] +} +``` + +## Identifier Type + +- **`atype: 1`** — The AdCOM agent type for web (`AgentTypeWeb`). This is used in EID emission. + +`atype` is an OpenRTB agent type (environment), not an IAB GVL vendor ID. + +## Debugging + +```javascript +pbjs.getUserIds().locId +pbjs.refreshUserIds() +localStorage.getItem('_locid') +localStorage.getItem('_locid_ip') // IP cache entry +``` + +## Validation Checklist + +- [ ] EID is present in bid requests when no processing restriction is signaled +- [ ] No network request occurs when a global processing restriction is signaled +- [ ] Stored IDs are reused across page loads diff --git a/modules/userId/eids.md b/modules/userId/eids.md index f6f62229f53..bdd8a0bb3e8 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -25,6 +25,14 @@ userIdAsEids = [ }] }, + { + source: 'locid.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { source: 'adserver.org', uids: [{ diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 8ffd8f83043..f7bea8fd9f8 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -91,6 +91,16 @@ pbjs.setConfig({ name: '_li_pbid', expires: 60 } + }, { + name: 'locId', + params: { + endpoint: 'https://id.example.com/locid' + }, + storage: { + type: 'html5', + name: '_locid', + expires: 7 + } }, { name: 'criteo', storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible diff --git a/test/build-logic/no_3384_spec.mjs b/test/build-logic/no_3384_spec.mjs new file mode 100644 index 00000000000..94cc47227ef --- /dev/null +++ b/test/build-logic/no_3384_spec.mjs @@ -0,0 +1,47 @@ +import {describe, it} from 'mocha'; +import {expect} from 'chai'; +import {execFileSync} from 'node:child_process'; +import {fileURLToPath} from 'node:url'; +import path from 'node:path'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +describe('build hygiene checks', () => { + it('should not contain the forbidden legacy token in LocID files', () => { + const forbiddenToken = ['33', '84'].join(''); + const scopeArgs = [ + 'ls-files', + '--', + ':(glob)modules/**/locId*', + ':(glob)test/spec/**/locId*', + 'docs/modules/locid.md', + 'modules/locIdSystem.md' + ]; + const scopedPaths = execFileSync('git', scopeArgs, { cwd: repoRoot, encoding: 'utf8' }) + .split('\n') + .map(filePath => filePath.trim()) + .filter(Boolean); + + expect(scopedPaths.length, 'No LocID files were selected for the 3384 guard').to.be.greaterThan(0); + + const args = [ + 'grep', + '-n', + '-I', + '-E', + `\\b${forbiddenToken}\\b`, + '--', + ...scopedPaths + ]; + + try { + const output = execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8' }); + expect(output.trim(), `Unexpected ${forbiddenToken} matches:\n${output}`).to.equal(''); + } catch (e) { + if (e?.status === 1) { + return; + } + throw e; + } + }); +}); diff --git a/test/spec/modules/locIdSystem_spec.js b/test/spec/modules/locIdSystem_spec.js new file mode 100644 index 00000000000..1b2fc02ddc8 --- /dev/null +++ b/test/spec/modules/locIdSystem_spec.js @@ -0,0 +1,2090 @@ +/** + * This file is licensed under the Apache 2.0 license. + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { locIdSubmodule, storage } from 'modules/locIdSystem.js'; +import { createEidsArray } from 'modules/userId/eids.js'; +import { attachIdSystem } from 'modules/userId/index.js'; +import * as ajax from 'src/ajax.js'; +import { VENDORLESS_GVLID } from 'src/consentHandler.js'; +import { uspDataHandler, gppDataHandler } from 'src/adapterManager.js'; +import { expect } from 'chai/index.mjs'; +import sinon from 'sinon'; + +const TEST_ID = 'SYybozbTuRaZkgGqCD7L7EE0FncoNUcx-om4xTfhJt36TFIAES2tF1qPH'; +const TEST_ENDPOINT = 'https://id.example.com/locid'; +const TEST_CONNECTION_IP = '203.0.113.42'; + +describe('LocID System', () => { + let sandbox; + let ajaxStub; + let ajaxBuilderStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(uspDataHandler, 'getConsentData').returns(null); + sandbox.stub(gppDataHandler, 'getConsentData').returns(null); + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'setDataInLocalStorage'); + ajaxStub = sandbox.stub(); + ajaxBuilderStub = sandbox.stub(ajax, 'ajaxBuilder').returns(ajaxStub); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('module properties', () => { + it('should expose correct module name', () => { + expect(locIdSubmodule.name).to.equal('locId'); + }); + + it('should have eids configuration with correct defaults', () => { + expect(locIdSubmodule.eids).to.be.an('object'); + expect(locIdSubmodule.eids.locId).to.be.an('object'); + expect(locIdSubmodule.eids.locId.source).to.equal('locid.com'); + // atype 1 = AdCOM AgentTypeWeb + expect(locIdSubmodule.eids.locId.atype).to.equal(1); + }); + + it('should register as a vendorless TCF module', () => { + expect(locIdSubmodule.gvlid).to.equal(VENDORLESS_GVLID); + }); + + it('should have getValue function that extracts ID', () => { + const getValue = locIdSubmodule.eids.locId.getValue; + expect(getValue('test-id')).to.equal('test-id'); + expect(getValue({ id: 'id-shape' })).to.equal('id-shape'); + expect(getValue({ locId: 'test-id' })).to.equal('test-id'); + expect(getValue({ locid: 'legacy-id' })).to.equal('legacy-id'); + }); + }); + + describe('decode', () => { + it('should decode valid ID correctly', () => { + const result = locIdSubmodule.decode({ id: TEST_ID, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.deep.equal({ locId: TEST_ID }); + }); + + it('should decode ID passed as object', () => { + const result = locIdSubmodule.decode({ id: TEST_ID, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.deep.equal({ locId: TEST_ID }); + }); + + it('should return undefined for invalid values', () => { + [null, undefined, '', {}, [], 123, TEST_ID].forEach(value => { + expect(locIdSubmodule.decode(value)).to.be.undefined; + }); + }); + + it('should return undefined when connection_ip is missing', () => { + expect(locIdSubmodule.decode({ id: TEST_ID })).to.be.undefined; + }); + + it('should return undefined for IDs exceeding max length', () => { + const longId = 'a'.repeat(513); + expect(locIdSubmodule.decode({ id: longId, connectionIp: TEST_CONNECTION_IP })).to.be.undefined; + }); + + it('should accept ID at exactly MAX_ID_LENGTH (512 characters)', () => { + const maxLengthId = 'a'.repeat(512); + const result = locIdSubmodule.decode({ id: maxLengthId, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.deep.equal({ locId: maxLengthId }); + }); + }); + + describe('getId', () => { + it('should return callback for async endpoint fetch', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + const result = locIdSubmodule.getId(config, {}); + expect(result).to.have.property('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('should call endpoint and return tx_cloc on success', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + expect(ajaxStub.calledOnce).to.be.true; + done(); + }); + }); + + it('should cache entry with null id when tx_cloc is missing but connection_ip is present', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ stable_cloc: 'stable-cloc-id-12345', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should prefer tx_cloc over stable_cloc when both present', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ + tx_cloc: TEST_ID, + stable_cloc: 'stable-fallback', + connection_ip: TEST_CONNECTION_IP + })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return undefined on endpoint error', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.error('Network error'); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + + it('should reuse storedId when valid and IP cache matches', () => { + // Set up IP cache matching stored entry's IP + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + name: '_locid' + } + }; + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + updatedAt: Date.now(), + expiresAt: Date.now() + 1000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(ajaxStub.called).to.be.false; + }); + + it('should not reuse storedId when expired', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + const storedId = { + id: 'expired-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: 1000, + updatedAt: 1000, + expiresAt: Date.now() - 1000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.have.property('callback'); + }); + + it('should not reuse storedId when connectionIp is missing', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + const storedId = { + id: 'existing-id', + createdAt: 1000, + updatedAt: 1000, + expiresAt: 2000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.have.property('callback'); + }); + + it('should pass x-api-key header when apiKey is configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.customHeaders).to.deep.equal({ 'x-api-key': 'test-api-key' }); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + apiKey: 'test-api-key' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should not include customHeaders when apiKey is not configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.customHeaders).to.not.exist; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should pass withCredentials when configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.withCredentials).to.be.true; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + withCredentials: true + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should default withCredentials to false', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.withCredentials).to.be.false; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should use default timeout of 800ms when timeoutMs is not configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + expect(ajaxBuilderStub.calledWith(800)).to.be.true; + done(); + }); + }); + + it('should use custom timeout when timeoutMs is configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + timeoutMs: 1500 + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + expect(ajaxBuilderStub.calledWith(1500)).to.be.true; + done(); + }); + }); + + it('should always use GET method', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(options.method).to.equal('GET'); + expect(body).to.be.null; + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should append alt_id query parameter when altId is configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT + '?alt_id=user123'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + altId: 'user123' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should use & separator when endpoint already has query params', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT + '?existing=param&alt_id=user456'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + '?existing=param', + altId: 'user456' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should URL-encode altId value', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT + '?alt_id=user%40example.com'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + altId: 'user@example.com' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should not append alt_id when altId is not configured', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal(TEST_ENDPOINT); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should preserve URL fragment when appending alt_id', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal('https://id.example.com/locid?alt_id=user123#frag'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: 'https://id.example.com/locid#frag', + altId: 'user123' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should preserve URL fragment when endpoint has existing query params', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + expect(url).to.equal('https://id.example.com/locid?x=1&alt_id=user456#frag'); + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: 'https://id.example.com/locid?x=1#frag', + altId: 'user456' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => done()); + }); + + it('should return undefined via callback when endpoint is empty string', (done) => { + const config = { + params: { + endpoint: '' + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + expect(ajaxStub.called).to.be.false; + done(); + }); + }); + }); + + describe('extendId', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + it('should return stored id when valid and IP cache is current', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 14400000 + })); + const storedId = { id: 'existing-id', connectionIp: TEST_CONNECTION_IP }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should reuse storedId when refreshInSeconds is configured but not due', () => { + const now = Date.now(); + const refreshInSeconds = 60; + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: now, + expiresAt: now + 14400000 + })); + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: now - ((refreshInSeconds - 10) * 1000), + expiresAt: now + 10000 + }; + const refreshConfig = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + refreshInSeconds + } + }; + const result = locIdSubmodule.extendId(refreshConfig, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should return undefined when refreshInSeconds is due', () => { + const now = Date.now(); + const refreshInSeconds = 60; + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + createdAt: now - ((refreshInSeconds + 10) * 1000), + expiresAt: now + 10000 + }; + const refreshConfig = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + refreshInSeconds + } + }; + const result = locIdSubmodule.extendId(refreshConfig, {}, storedId); + expect(result).to.be.undefined; + }); + + it('should return undefined when refreshInSeconds is configured and createdAt is missing', () => { + const now = Date.now(); + const refreshInSeconds = 60; + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + expiresAt: now + 10000 + }; + const refreshConfig = { + params: { + endpoint: TEST_ENDPOINT + }, + storage: { + refreshInSeconds + } + }; + const result = locIdSubmodule.extendId(refreshConfig, {}, storedId); + expect(result).to.be.undefined; + }); + + it('should return undefined when storedId is a string', () => { + const result = locIdSubmodule.extendId(config, {}, 'existing-id'); + expect(result).to.be.undefined; + }); + + it('should return undefined when connectionIp is missing', () => { + const result = locIdSubmodule.extendId(config, {}, { id: 'existing-id' }); + expect(result).to.be.undefined; + }); + + it('should return undefined when stored entry is expired', () => { + const storedId = { + id: 'existing-id', + connectionIp: TEST_CONNECTION_IP, + expiresAt: Date.now() - 1000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.be.undefined; + }); + }); + + describe('privacy framework handling', () => { + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + // --- Tests for no privacy signals (LI-based operation) --- + // LocID operates under Legitimate Interest and should proceed when no + // privacy framework is present, unless requirePrivacySignals is enabled. + + it('should proceed when no consentData provided at all (default LI-based operation)', () => { + // When no privacy signals are present, module should proceed by default + const result = locIdSubmodule.getId(config, undefined); + expect(result).to.have.property('callback'); + }); + + it('should proceed when consentData is null (default LI-based operation)', () => { + const result = locIdSubmodule.getId(config, null); + expect(result).to.have.property('callback'); + }); + + it('should proceed when consentData is empty object (default LI-based operation)', () => { + // Empty object has no privacy signals, so should proceed + const result = locIdSubmodule.getId(config, {}); + expect(result).to.have.property('callback'); + }); + + it('should return undefined when no consentData and requirePrivacySignals=true', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const result = locIdSubmodule.getId(strictConfig, undefined); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined when empty consentData and requirePrivacySignals=true', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const result = locIdSubmodule.getId(strictConfig, {}); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined when no consentData and privacyMode=requireSignals', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + privacyMode: 'requireSignals' + } + }; + const result = locIdSubmodule.getId(strictConfig, undefined); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should proceed when no consentData and privacyMode=allowWithoutSignals', () => { + const permissiveConfig = { + params: { + endpoint: TEST_ENDPOINT, + privacyMode: 'allowWithoutSignals' + } + }; + const result = locIdSubmodule.getId(permissiveConfig, undefined); + expect(result).to.have.property('callback'); + }); + + it('should proceed with privacy signals present and requirePrivacySignals=true', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: {} + } + }; + const result = locIdSubmodule.getId(strictConfig, consentData); + expect(result).to.have.property('callback'); + }); + + it('should detect privacy signals from uspDataHandler and block on processing restriction', () => { + // Restore and re-stub to return USP processing restriction signal + uspDataHandler.getConsentData.restore(); + sandbox.stub(uspDataHandler, 'getConsentData').returns('1YY-'); + + // Even with empty consentData, handler provides privacy signal + const result = locIdSubmodule.getId(config, {}); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should detect privacy signals from gppDataHandler and block on processing restriction', () => { + // Restore and re-stub to return GPP processing restriction signal + gppDataHandler.getConsentData.restore(); + sandbox.stub(gppDataHandler, 'getConsentData').returns({ + applicableSections: [7], + parsedSections: { + usnat: { + KnownChildSensitiveDataConsents: [1] + } + } + }); + + // Even with empty consentData, handler provides privacy signal + const result = locIdSubmodule.getId(config, {}); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should proceed when uspDataHandler returns non-restrictive value', () => { + // Restore and re-stub to return non-restrictive USP + uspDataHandler.getConsentData.restore(); + sandbox.stub(uspDataHandler, 'getConsentData').returns('1NN-'); + + const result = locIdSubmodule.getId(config, {}); + expect(result).to.have.property('callback'); + }); + + // --- Tests for gdprApplies edge cases (LI-based operation) --- + // gdprApplies alone does NOT constitute a privacy signal. + // A GDPR signal requires actual CMP artifacts (consentString or vendorData). + + it('should proceed when gdprApplies=true but no consentString/vendorData (default LI mode)', () => { + // gdprApplies alone is not a signal - allows LI-based operation without CMP + const consentData = { + gdprApplies: true + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when gdprApplies=true and usp is present but no GDPR CMP artifacts', () => { + const consentData = { + gdprApplies: true, + usp: '1NN-' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when gdprApplies=true and consentString is empty (treated as absent CMP artifact)', () => { + // Empty consentString is treated as not present, so LI-mode behavior applies. + const consentData = { + gdprApplies: true, + consentString: '' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should return undefined when gdprApplies=true, no CMP artifacts, and strict mode', () => { + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const consentData = { + gdprApplies: true + }; + const result = locIdSubmodule.getId(strictConfig, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined when gdprApplies=true, vendorData present but no consentString', () => { + // vendorData presence = CMP is present, so signals ARE present + // GDPR applies + signals present + no consentString → deny + const consentData = { + gdprApplies: true, + vendorData: { + vendor: {} + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should deny when gdprApplies=false and strict mode is enabled (gdprApplies alone is not a signal)', () => { + // gdprApplies=false alone is not a signal, so strict mode blocks + const strictConfig = { + params: { + endpoint: TEST_ENDPOINT, + requirePrivacySignals: true + } + }; + const consentData = { + gdprApplies: false + }; + const result = locIdSubmodule.getId(strictConfig, consentData); + // gdprApplies=false is not a signal, so strict mode denies + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + // --- Original tests for when privacy signals ARE present --- + + it('should proceed with valid GDPR framework data', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR does not apply (no CMP artifacts)', () => { + // gdprApplies=false alone is not a signal - LI operation allowed + const consentData = { + gdprApplies: false + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies with consentString and vendor flags deny', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: { 999: false } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should return undefined on US Privacy processing restriction and not call ajax', () => { + const consentData = { + usp: '1YY-' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined on legacy US Privacy processing restriction and not call ajax', () => { + const consentData = { + uspConsent: '1YY-' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined on GPP processing restriction and not call ajax', () => { + const consentData = { + gpp: { + applicableSections: [7], + parsedSections: { + usnat: { + KnownChildSensitiveDataConsents: [1] + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should return undefined on legacy GPP processing restriction and not call ajax', () => { + const consentData = { + gppConsent: { + applicableSections: [7], + parsedSections: { + usnat: { + KnownChildSensitiveDataConsents: [1] + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.be.undefined; + expect(ajaxStub.called).to.be.false; + }); + + it('should proceed when nested GDPR applies but no CMP artifacts (LI operation)', () => { + // Empty consentString in nested gdpr object is not a signal - LI operation allowed + const consentData = { + gdpr: { + gdprApplies: true, + consentString: '' + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed with valid nested GDPR framework data', () => { + const consentData = { + gdpr: { + gdprApplies: true, + consentString: 'valid-consent-string' + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendorData is present', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: { 999: false } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendor is missing from consents', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 1234: true }, + legitimateInterests: { 1234: true } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendor consents object is present', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: true } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when GDPR applies and vendor legitimate interests object is present', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: { 999: true } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendorData is not available (cannot determine vendor permission)', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string' + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should check nested vendorData when using gdpr object shape', () => { + const consentData = { + gdpr: { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: true } + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when nested vendorData has explicit deny flags', () => { + const consentData = { + gdpr: { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: { 999: false }, + legitimateInterests: {} + } + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendor consents is a function returning true', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: (id) => id === 999, + legitimateInterests: {} + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendor legitimateInterests is a function returning true', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: (id) => false, + legitimateInterests: (id) => id === 999 + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + + it('should proceed when vendor consent callbacks both return false', () => { + const consentData = { + gdprApplies: true, + consentString: 'valid-consent-string', + vendorData: { + vendor: { + consents: (id) => false, + legitimateInterests: (id) => false + } + } + }; + const result = locIdSubmodule.getId(config, consentData); + expect(result).to.have.property('callback'); + }); + }); + + describe('response parsing', () => { + it('should parse JSON string response', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success('{"tx_cloc":"parsed-id","connection_ip":"203.0.113.42"}'); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal('parsed-id'); + expect(id.connectionIp).to.equal('203.0.113.42'); + done(); + }); + }); + + it('should return undefined when connection_ip is missing', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + + it('should return undefined for invalid JSON', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success('not valid json'); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + + it('should cache entry with null id when tx_cloc is empty string', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: '', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should cache entry with null id when tx_cloc is whitespace-only', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ tx_cloc: ' \n\t ', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return undefined when tx_cloc is missing', (done) => { + ajaxStub.callsFake((url, callbacks, body, options) => { + callbacks.success(JSON.stringify({ other_field: 'value' })); + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT + } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined; + done(); + }); + }); + }); + + describe('empty tx_cloc handling', () => { + it('should decode null id as undefined (no EID emitted)', () => { + const result = locIdSubmodule.decode({ id: null, connectionIp: TEST_CONNECTION_IP }); + expect(result).to.be.undefined; + }); + + it('should cache empty tx_cloc response for the full cache period', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { expires: 7 } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + expect(id.expiresAt).to.be.a('number'); + expect(id.expiresAt).to.be.greaterThan(Date.now()); + done(); + }); + }); + + it('should reuse cached entry with null id on subsequent getId calls', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: null, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + updatedAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(ajaxStub.called).to.be.false; + }); + + it('should not produce EID when tx_cloc is null', () => { + const decoded = locIdSubmodule.decode({ id: null, connectionIp: TEST_CONNECTION_IP }); + expect(decoded).to.be.undefined; + }); + + it('should write IP cache when endpoint returns empty tx_cloc', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + expect(storage.setDataInLocalStorage.called).to.be.true; + const callArgs = storage.setDataInLocalStorage.getCall(0).args; + expect(callArgs[0]).to.equal('_locid_ip'); + const ipEntry = JSON.parse(callArgs[1]); + expect(ipEntry.ip).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + }); + + describe('IP cache management', () => { + const ipCacheConfig = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + it('should read valid IP cache entry', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + // If IP cache is valid and stored entry matches, should reuse + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(ipCacheConfig, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + expect(ajaxStub.called).to.be.false; + }); + + it('should treat expired IP cache as missing', (done) => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now() - 20000, + expiresAt: Date.now() - 1000 + })); + + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(ipCacheConfig, {}, storedId); + // Expired IP cache → calls main endpoint + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should handle corrupted IP cache JSON gracefully', (done) => { + storage.getDataFromLocalStorage.returns('not-valid-json'); + + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const result = locIdSubmodule.getId(ipCacheConfig, {}); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id).to.be.an('object'); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should derive IP cache key from storage name', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: 'custom_key' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + const setCall = storage.setDataInLocalStorage.getCall(0); + expect(setCall.args[0]).to.equal('custom_key_ip'); + done(); + }); + }); + + it('should use custom ipCacheName when configured', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT, ipCacheName: 'my_ip_cache' }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + const setCall = storage.setDataInLocalStorage.getCall(0); + expect(setCall.args[0]).to.equal('my_ip_cache'); + done(); + }); + }); + + it('should use custom ipCacheTtlMs when configured', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT, ipCacheTtlMs: 7200000 }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback(() => { + const setCall = storage.setDataInLocalStorage.getCall(0); + const ipEntry = JSON.parse(setCall.args[1]); + // TTL should be ~2 hours + const ttl = ipEntry.expiresAt - ipEntry.fetchedAt; + expect(ttl).to.equal(7200000); + done(); + }); + }); + + it('should write IP cache on every successful main endpoint response', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '10.0.0.1' })); + }); + + const result = locIdSubmodule.getId(ipCacheConfig, {}); + result.callback(() => { + expect(storage.setDataInLocalStorage.called).to.be.true; + const ipEntry = JSON.parse(storage.setDataInLocalStorage.getCall(0).args[1]); + expect(ipEntry.ip).to.equal('10.0.0.1'); + done(); + }); + }); + }); + + describe('getId with ipEndpoint (two-call optimization)', () => { + it('should call ipEndpoint first when IP cache is expired', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success(JSON.stringify({ ip: '10.0.0.1' })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '10.0.0.1' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + // IP changed (no stored entry) → 2 calls: ipEndpoint + main endpoint + expect(callCount).to.equal(2); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should reuse cached tx_cloc when ipEndpoint returns same IP', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success(JSON.stringify({ ip: TEST_CONNECTION_IP })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: 'new-id', connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP unchanged → only 1 call (ipEndpoint), reuses stored tx_cloc + expect(callCount).to.equal(1); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should call main endpoint when ipEndpoint returns different IP', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success(JSON.stringify({ ip: '10.0.0.99' })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: 'new-id', connection_ip: '10.0.0.99' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP changed → 2 calls: ipEndpoint + main endpoint + expect(callCount).to.equal(2); + expect(id.id).to.equal('new-id'); + done(); + }); + }); + + it('should fall back to main endpoint when ipEndpoint fails', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.error('Network error'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(callCount).to.equal(2); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should fall back to main endpoint when ipEndpoint returns invalid IP', (done) => { + let callCount = 0; + ajaxStub.callsFake((url, callbacks) => { + callCount++; + if (url === 'https://ip.example.com/check') { + callbacks.success('not-an-ip!!!'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(callCount).to.equal(2); + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + + it('should pass apiKey header to ipEndpoint when configured', (done) => { + ajaxStub.callsFake((url, callbacks, _body, options) => { + if (url === 'https://ip.example.com/check') { + expect(options.customHeaders).to.deep.equal({ 'x-api-key': 'test-key-123' }); + callbacks.success(JSON.stringify({ ip: TEST_CONNECTION_IP })); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check', + apiKey: 'test-key-123' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should parse plain text IP from ipEndpoint', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success(TEST_CONNECTION_IP); + } else { + callbacks.success(JSON.stringify({ tx_cloc: 'new-id', connection_ip: TEST_CONNECTION_IP })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP same → reuses stored tx_cloc + expect(id.id).to.equal(TEST_ID); + done(); + }); + }); + }); + + describe('getId tx_cloc preservation (no churn)', () => { + it('should preserve existing tx_cloc when main endpoint returns same IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP unchanged → preserve existing tx_cloc (don't churn) + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should use fresh non-null tx_cloc when stored entry is null for same IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: null, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should use fresh tx_cloc when main endpoint returns different IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: '10.0.0.99' })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // IP changed → use fresh tx_cloc + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal('10.0.0.99'); + done(); + }); + }); + + it('should use fresh tx_cloc when stored entry is expired even if IP matches', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now() - 86400000, + expiresAt: Date.now() - 1000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // tx_cloc expired → use fresh even though IP matches + expect(id.id).to.equal('fresh-id'); + done(); + }); + }); + + it('should honor null tx_cloc from main endpoint even when stored entry is reusable', (done) => { + // Server returns connection_ip but no tx_cloc → freshEntry.id === null. + // The stored entry has a valid tx_cloc for the same IP, but the server + // now indicates no ID for this IP. The null response must be honored. + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + + const result = locIdSubmodule.getId(config, {}, storedId); + result.callback((id) => { + // Server said no tx_cloc → stored entry must NOT be preserved + expect(id.id).to.be.null; + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should use fresh entry on first load (no stored entry)', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: TEST_CONNECTION_IP })); + }); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id.id).to.equal(TEST_ID); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + }); + + describe('extendId with null id and IP cache', () => { + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + + it('should accept stored entry with null id (empty tx_cloc)', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 14400000 + })); + const storedId = { + id: null, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should return refresh callback when IP cache shows different IP', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: '10.0.0.99' })); + }); + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: '10.0.0.99', + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal('10.0.0.99'); + done(); + }); + }); + + it('should extend when IP cache matches stored entry IP', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should return refresh callback when IP cache is missing', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return refresh callback when IP cache is expired', (done) => { + ajaxStub.callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ tx_cloc: 'fresh-id', connection_ip: TEST_CONNECTION_IP })); + }); + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now() - 14400000, + expiresAt: Date.now() - 1000 + })); + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.have.property('callback'); + result.callback((id) => { + expect(id.id).to.equal('fresh-id'); + expect(id.connectionIp).to.equal(TEST_CONNECTION_IP); + done(); + }); + }); + + it('should return undefined for undefined id (not null, not valid string)', () => { + const storedId = { + id: undefined, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.extendId(config, {}, storedId); + expect(result).to.be.undefined; + }); + }); + + describe('normalizeStoredId with null id', () => { + it('should preserve explicit null id', () => { + // getId is the public interface; test via decode which uses normalized values + const stored = { id: null, connectionIp: TEST_CONNECTION_IP }; + // decode returns undefined for null id (correct: no EID emitted) + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.be.undefined; + }); + + it('should preserve valid string id', () => { + const stored = { id: TEST_ID, connectionIp: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + }); + + it('should fall back to tx_cloc when id key is absent', () => { + const stored = { tx_cloc: TEST_ID, connectionIp: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + }); + + it('should handle connection_ip alias', () => { + const stored = { id: TEST_ID, connection_ip: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + }); + }); + + describe('parseIpResponse via ipEndpoint', () => { + it('should parse JSON with ip field', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success('{"ip":"1.2.3.4"}'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '1.2.3.4' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + // IP fetched and used + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should parse JSON with connection_ip field', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success('{"connection_ip":"1.2.3.4"}'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '1.2.3.4' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + + it('should parse plain text IP address', (done) => { + ajaxStub.callsFake((url, callbacks) => { + if (url === 'https://ip.example.com/check') { + callbacks.success('1.2.3.4\n'); + } else { + callbacks.success(JSON.stringify({ tx_cloc: TEST_ID, connection_ip: '1.2.3.4' })); + } + }); + + const config = { + params: { + endpoint: TEST_ENDPOINT, + ipEndpoint: 'https://ip.example.com/check' + }, + storage: { name: '_locid' } + }; + + const result = locIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.an('object'); + done(); + }); + }); + }); + + describe('backward compatibility', () => { + it('should work with existing stored entries that have string ids', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result).to.deep.equal({ id: storedId }); + }); + + it('should work with stored entries using connection_ip alias', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + id: TEST_ID, + connection_ip: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result.id.connectionIp).to.equal(TEST_CONNECTION_IP); + }); + + it('should work with stored entries using tx_cloc alias', () => { + storage.getDataFromLocalStorage.returns(JSON.stringify({ + ip: TEST_CONNECTION_IP, + fetchedAt: Date.now(), + expiresAt: Date.now() + 1000 + })); + + const config = { + params: { endpoint: TEST_ENDPOINT }, + storage: { name: '_locid' } + }; + const storedId = { + tx_cloc: TEST_ID, + connectionIp: TEST_CONNECTION_IP, + createdAt: Date.now(), + expiresAt: Date.now() + 86400000 + }; + const result = locIdSubmodule.getId(config, {}, storedId); + expect(result.id.id).to.equal(TEST_ID); + }); + }); + + describe('EID round-trip integration', () => { + before(() => { + attachIdSystem(locIdSubmodule); + }); + + it('should produce a valid EID from decode output via createEidsArray', () => { + // Simulate the full Prebid pipeline: + // 1. decode() returns { locId: "string" } + // 2. Prebid extracts idObj["locId"] -> the string + // 3. createEidsArray({ locId: "string" }) should produce a valid EID + const stored = { id: TEST_ID, connectionIp: TEST_CONNECTION_IP }; + const decoded = locIdSubmodule.decode(stored); + expect(decoded).to.deep.equal({ locId: TEST_ID }); + + const eids = createEidsArray(decoded); + expect(eids.length).to.equal(1); + expect(eids[0]).to.deep.equal({ + source: 'locid.com', + uids: [{ id: TEST_ID, atype: 1 }] + }); + expect(eids[0].uids[0].atype).to.not.equal(Number('33' + '84')); + }); + + it('should not produce EID when decode returns undefined', () => { + const decoded = locIdSubmodule.decode(null); + expect(decoded).to.be.undefined; + }); + + it('should not produce EID when only stable_cloc is present', () => { + const decoded = locIdSubmodule.decode({ + stable_cloc: 'stable-only-value', + connectionIp: TEST_CONNECTION_IP + }); + expect(decoded).to.be.undefined; + + const eids = createEidsArray({ locId: decoded?.locId }); + expect(eids).to.deep.equal([]); + }); + + it('should not produce EID when tx_cloc is null', () => { + const decoded = locIdSubmodule.decode({ id: null, connectionIp: TEST_CONNECTION_IP }); + expect(decoded).to.be.undefined; + + const eids = createEidsArray({ locId: decoded?.locId }); + expect(eids).to.deep.equal([]); + }); + + it('should not produce EID when tx_cloc is missing', () => { + const decoded = locIdSubmodule.decode({ connectionIp: TEST_CONNECTION_IP }); + expect(decoded).to.be.undefined; + + const eids = createEidsArray({ locId: decoded?.locId }); + expect(eids).to.deep.equal([]); + }); + }); +}); From 341735e4864a75adad3f4d9a829cd5bd0e37c30f Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 10 Feb 2026 12:56:12 -0500 Subject: [PATCH 032/105] Github Actions: bump download artifact (#14440) * CI: harden artifact restore for browserstack workflows * Update run-tests.yml * Fix comment formatting in test.yml * Retry on artifact download failure --------- Co-authored-by: Demetrio Girardi --- .github/actions/load/action.yml | 10 +++++++--- .github/actions/unzip-artifact/action.yml | 3 +++ .github/workflows/run-tests.yml | 5 +++++ .github/workflows/test.yml | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/actions/load/action.yml b/.github/actions/load/action.yml index 0102608dbd1..e3fc00dc6ae 100644 --- a/.github/actions/load/action.yml +++ b/.github/actions/load/action.yml @@ -24,10 +24,14 @@ runs: rm -r "$(pwd)"/* - name: Download artifact - uses: actions/download-artifact@v5 + uses: Wandalen/wretry.action@v3.8.0 with: - path: '${{ runner.temp }}' - name: '${{ inputs.name }}' + action: actions/download-artifact@v7 + attempt_limit: 2 + attempt_delay: 10000 + with: | + path: '${{ runner.temp }}' + name: '${{ inputs.name }}' - name: 'Untar working directory' shell: bash diff --git a/.github/actions/unzip-artifact/action.yml b/.github/actions/unzip-artifact/action.yml index 672fae7af85..a05c15a00cd 100644 --- a/.github/actions/unzip-artifact/action.yml +++ b/.github/actions/unzip-artifact/action.yml @@ -11,6 +11,9 @@ outputs: runs: using: 'composite' steps: + - name: 'Delay waiting for artifacts to be ready' + shell: bash + run: sleep 10 - name: 'Download artifact' id: download uses: actions/github-script@v8 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1cc6808f29f..c9386396ae5 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -71,6 +71,11 @@ on: BROWSERSTACK_ACCESS_KEY: description: "Browserstack access key" + +permissions: + contents: read + actions: read + jobs: checkout: name: "Define chunks" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1209ca2057d..ed12de000c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,10 @@ on: pull_request: types: [opened, synchronize, reopened] +permissions: + contents: read + actions: read + concurrency: group: test-${{ github.head_ref || github.ref }} cancel-in-progress: true From ea803f129eb3bd2a296d1ac2b13954306f169bcd Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 10 Feb 2026 12:56:33 -0500 Subject: [PATCH 033/105] 33acrossId System: stabilize ID wipe unit tests (#14441) --- test/spec/modules/33acrossIdSystem_spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 3cf5995bb17..3a1bf6da2b3 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -260,6 +260,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, { @@ -277,6 +278,7 @@ describe('33acrossIdSystem', () => { removeDataFromLocalStorage.restore(); setCookie.restore(); + cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); }); }); @@ -419,6 +421,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, { @@ -436,6 +439,7 @@ describe('33acrossIdSystem', () => { removeDataFromLocalStorage.restore(); setCookie.restore(); + cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); }); }); From 4ec1f1c7f0d4325619c0036256c2aa6afa3e1b44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:35:54 -0500 Subject: [PATCH 034/105] Bump axios from 1.13.2 to 1.13.5 (#14443) Bumps [axios](https://github.com/axios/axios) from 1.13.2 to 1.13.5. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.13.2...v1.13.5) --- updated-dependencies: - dependency-name: axios dependency-version: 1.13.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 60 +++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index f10860da566..067718a8f15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7193,8 +7193,9 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/atob": { "version": "2.1.2", @@ -7222,14 +7223,13 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "dev": true, - "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -8219,8 +8219,9 @@ }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -9279,8 +9280,9 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -11730,7 +11732,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -11738,7 +11742,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -11813,11 +11816,10 @@ "license": "BSD" }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -26894,6 +26896,8 @@ }, "asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "atob": { @@ -26908,13 +26912,13 @@ } }, "axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "dev": true, "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -27557,6 +27561,8 @@ }, "combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { "delayed-stream": "~1.0.0" @@ -28236,6 +28242,8 @@ }, "delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, "depd": { @@ -29852,7 +29860,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.6", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true }, "for-each": { @@ -29892,9 +29902,9 @@ "dev": true }, "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "requires": { "asynckit": "^0.4.0", From 88841dbd66c1483ac4a062c1f1cef169792d3142 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 10 Feb 2026 14:54:45 -0800 Subject: [PATCH 035/105] Core: update storage disclosure for prebid.storage (#14442) --- metadata/core.json | 6 ++++++ metadata/disclosures/prebid/probes.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/metadata/core.json b/metadata/core.json index 1e43a89e586..faacd3ceed4 100644 --- a/metadata/core.json +++ b/metadata/core.json @@ -11,6 +11,12 @@ "moduleName": "prebid-core", "disclosureURL": "local://prebid/probes.json" }, + { + "componentType": "prebid", + "componentName": "storage", + "moduleName": "prebid-core", + "disclosureURL": "local://prebid/probes.json" + }, { "componentType": "prebid", "componentName": "debugging", diff --git a/metadata/disclosures/prebid/probes.json b/metadata/disclosures/prebid/probes.json index c371cef1d4e..16cc5dec160 100644 --- a/metadata/disclosures/prebid/probes.json +++ b/metadata/disclosures/prebid/probes.json @@ -22,7 +22,7 @@ "domains": [ { "domain": "*", - "use": "Temporary 'probing' cookies are written (and deleted) to determine the top-level domain; likewise, probes are temporarily written to local and sessionStorage to determine their availability" + "use": "Temporary 'probing' cookies are written (and deleted) to determine the top-level domain and availability of cookies; likewise, probes are temporarily written to local and sessionStorage to determine their availability" } ] } From 3e8349315e0d9b91b09a1d1058515a841a4f07be Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 11 Feb 2026 10:29:54 -0500 Subject: [PATCH 036/105] Build process: Add .cache to gulp clean (#14438) * build: include webpack cache in clean task * Remove comment from clean function Removed comment about webpack filesystem cache in clean function. --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index e5a4db41884..cc543ad73ec 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -53,7 +53,7 @@ function bundleToStdout() { bundleToStdout.displayName = 'bundle-to-stdout'; function clean() { - return gulp.src(['build', 'dist'], { + return gulp.src(['.cache', 'build', 'dist'], { read: false, allowEmpty: true }) From 8a10fc8b686aca74c4a147d778a4170428b97fb1 Mon Sep 17 00:00:00 2001 From: ysfbsf Date: Wed, 11 Feb 2026 16:30:05 +0100 Subject: [PATCH 037/105] Add GPP consent support to user sync URL in Missena adapter (#14436) --- modules/missenaBidAdapter.js | 8 +++ test/spec/modules/missenaBidAdapter_spec.js | 64 +++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 573f20fb302..dba7a723693 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -157,6 +157,7 @@ export const spec = { serverResponses, gdprConsent = {}, uspConsent, + gppConsent, ) { if (!syncOptions.iframeEnabled || !this.msnaApiKey) { return []; @@ -172,6 +173,13 @@ export const spec = { if (uspConsent) { url.searchParams.append('us_privacy', uspConsent); } + if (gppConsent?.gppString) { + url.searchParams.append('gpp', gppConsent.gppString); + url.searchParams.append( + 'gpp_sid', + (gppConsent.applicableSections || []).join(','), + ); + } return [{ type: 'iframe', url: url.href }]; }, diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index f4e09a981fe..c52f738ca55 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -367,5 +367,69 @@ describe('Missena Adapter', function () { expect(userSync[0].type).to.be.equal('iframe'); expect(userSync[0].url).to.be.equal(expectedUrl); }); + + it('sync frame url should contain gpp data when present', function () { + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7, 8], + }; + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + {}, + undefined, + gppConsent, + ); + expect(userSync.length).to.be.equal(1); + expect(userSync[0].type).to.be.equal('iframe'); + const syncUrl = new URL(userSync[0].url); + expect(syncUrl.searchParams.get('gpp')).to.equal(gppConsent.gppString); + expect(syncUrl.searchParams.get('gpp_sid')).to.equal('7,8'); + }); + + it('sync frame url should not contain gpp data when gppConsent is undefined', function () { + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + {}, + undefined, + undefined, + ); + expect(userSync.length).to.be.equal(1); + expect(userSync[0].url).to.not.contain('gpp'); + }); + + it('sync frame url should not contain gpp data when gppString is empty', function () { + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + {}, + undefined, + { gppString: '', applicableSections: [7] }, + ); + expect(userSync.length).to.be.equal(1); + expect(userSync[0].url).to.not.contain('gpp'); + }); + + it('sync frame url should contain all consent params together', function () { + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7], + }; + const userSync = spec.getUserSyncs( + iframeEnabledOptions, + [], + { gdprApplies: true, consentString }, + '1YNN', + gppConsent, + ); + expect(userSync.length).to.be.equal(1); + const syncUrl = new URL(userSync[0].url); + expect(syncUrl.searchParams.get('gdpr')).to.equal('1'); + expect(syncUrl.searchParams.get('gdpr_consent')).to.equal(consentString); + expect(syncUrl.searchParams.get('us_privacy')).to.equal('1YNN'); + expect(syncUrl.searchParams.get('gpp')).to.equal(gppConsent.gppString); + expect(syncUrl.searchParams.get('gpp_sid')).to.equal('7'); + }); }); }); From 093a4d7ad286713a738ada4d32515acf740c31d8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 11 Feb 2026 10:30:25 -0500 Subject: [PATCH 038/105] Agent guidelines: Add context for repo history access (#14430) Added additional context for accessing repo history. --- AGENTS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index ce4e1353a9a..c340fee56ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,3 +45,6 @@ This file contains instructions for the Codex agent and its friends when working - Avoid running Babel over the entire project for incremental test runs. - Use `gulp serve-and-test --file ` or `gulp test --file` so Babel processes only the specified files. - Do not invoke commands that rebuild all modules when only a subset are changed. + +## 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. From a9f328163556011f0d35ec651a0ce8dd654fb19f Mon Sep 17 00:00:00 2001 From: Dmitry Borisenko Date: Wed, 11 Feb 2026 20:46:56 +0500 Subject: [PATCH 039/105] Set alwaysHasCapacity for Sovrn bid adapter (#14454) --- modules/sovrnBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 2e38a88c2f4..1b337fd30cf 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -39,6 +39,7 @@ export const spec = { code: 'sovrn', supportedMediaTypes: [BANNER, VIDEO], gvlid: 13, + alwaysHasCapacity: true, /** * Check if the bid is a valid zone ID in either number or string form From f7c34c3ee899f9cc4d36f246e19b3ccb085d29e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petric=C4=83=20Nanc=C4=83?= Date: Wed, 11 Feb 2026 19:14:36 +0200 Subject: [PATCH 040/105] Sevio bid adapter fallback natives (#14390) * [SevioBidAdapter] - fix mapping by id for the native ads * [SevioBidAdapter] - fix mapping by id for the native ads * Add native parsing tests --- modules/sevioBidAdapter.js | 43 ++++++++- test/spec/modules/sevioBidAdapter_spec.js | 104 ++++++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index 66bfca681d9..b5db12b1cf5 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -43,6 +43,39 @@ const normalizeKeywords = (input) => { return []; }; +function resolveDataType(asset) { + if (typeof asset?.data?.type === 'number') { + return asset.data.type; + } + + if (typeof asset?.id === 'number') { + return asset.id; + } + + return null; +} + +// Helper: resolve the "image type" for an asset +// Returns 1 (icon), 3 (image) or null if unknown +function resolveImageType(asset) { + if (!asset) return null; + + // 1) explicit image type in the img block (preferred) + if (typeof asset.img?.type === 'number') return asset.img.type; + + // 2) fallback to data.type (some bidders put the type here) + if (typeof asset.data?.type === 'number') return asset.data.type; + + // 3) last resort: map legacy asset.id values to image types + // (13 -> icon, 14 -> image) — keep this mapping isolated here + if (typeof asset.id === 'number') { + if (asset.id === 13) return 1; // icon + if (asset.id === 14) return 3; // image + } + + return null; +} + const parseNativeAd = function (bid) { try { const nativeAd = JSON.parse(bid.ad); @@ -54,7 +87,8 @@ const parseNativeAd = function (bid) { } if (asset.data) { const value = asset.data.value; - switch (asset.data.type) { + const type = resolveDataType(asset); + switch (type) { case 1: if (value) native.sponsored = value; break; case 2: if (value) native.desc = value; break; case 3: if (value) native.rating = value; break; @@ -71,13 +105,14 @@ const parseNativeAd = function (bid) { } } if (asset.img) { - const { url, w = 0, h = 0, type } = asset.img; + const { url, w = 0, h = 0 } = asset.img; + const imgType = resolveImageType(asset); - if (type === 1 && url) { + if (imgType === 1 && url) { native.icon = url; native.icon_width = w; native.icon_height = h; - } else if (type === 3 && url) { + } else if (imgType === 3 && url) { native.image = url; native.image_width = w; native.image_height = h; diff --git a/test/spec/modules/sevioBidAdapter_spec.js b/test/spec/modules/sevioBidAdapter_spec.js index 63b36465dad..ce03c1baf12 100644 --- a/test/spec/modules/sevioBidAdapter_spec.js +++ b/test/spec/modules/sevioBidAdapter_spec.js @@ -521,4 +521,108 @@ describe('sevioBidAdapter', function () { expect(requests[0].data.keywords.tokens).to.deep.equal(['play', 'games', 'fun']); }); }); + + describe('native parsing', function () { + it('parses native assets: title, data->desc (type 2), image (asset.img), clickUrl and trackers', function () { + const serverResponseNative = { + body: { + bids: [ + { + requestId: 'native-1', + cpm: 1.0, + currency: 'EUR', + width: 1, + height: 1, + creativeId: 'native-creative-1', + ad: JSON.stringify({ + ver: '1.2', + assets: [ + { id: 2, title: { text: 'Native Title' } }, + { id: 4, data: { type: 2, value: 'Native body text' } }, + { id: 5, img: { type: 3, url: 'https://img.example/x.png', w: 120, h: 60 } } + ], + eventtrackers: [ + { event: 1, method: 1, url: 'https://impr.example/1' }, + { event: 2, method: 1, url: 'https://view.example/1' } + ], + link: { url: 'https://click.example', clicktrackers: ['https://clickt.example/1'] } + }), + ttl: 300, + netRevenue: true, + mediaType: 'NATIVE', + meta: { advertiserDomains: ['adv.example'] }, + bidder: 'sevio' + } + ] + } + }; + + const result = spec.interpretResponse(serverResponseNative); + expect(result).to.be.an('array').with.lengthOf(1); + + const out = result[0]; + expect(out).to.have.property('native'); + + const native = out.native; + expect(native.title).to.equal('Native Title'); + expect(native.image).to.equal('https://img.example/x.png'); + expect(native.image_width).to.equal(120); + expect(native.image_height).to.equal(60); + expect(native.clickUrl).to.equal('https://click.example'); + + expect(native.impressionTrackers).to.be.an('array').that.includes('https://impr.example/1'); + expect(native.viewableTrackers).to.be.an('array').that.includes('https://view.example/1'); + expect(native.clickTrackers).to.be.an('array').that.includes('https://clickt.example/1'); + + // meta preserved + expect(out.meta).to.have.property('advertiserDomains').that.deep.equals(['adv.example']); + }); + + it('maps legacy asset.id -> image types (13 -> icon, 14 -> image) and sets icon fields', function () { + const serverResponseIcon = { + body: { + bids: [ + { + requestId: 'native-icon', + cpm: 1.0, + currency: 'EUR', + width: 1, + height: 1, + creativeId: 'native-creative-icon', + ad: JSON.stringify({ + ver: '1.2', + assets: [ + // legacy asset id 13 should map to icon (img type 1) + { id: 13, img: { url: 'https://img.example/icon.png', w: 50, h: 50 } }, + // legacy asset id 14 should map to image (img type 3) + { id: 14, img: { url: 'https://img.example/img.png', w: 200, h: 100 } }, + { id: 2, title: { text: 'Legacy Mapping Test' } } + ], + link: { url: 'https://click.example/leg' } + }), + ttl: 300, + netRevenue: true, + mediaType: 'NATIVE', + meta: { advertiserDomains: ['legacy.example'] }, + bidder: 'sevio' + } + ] + } + }; + + const result = spec.interpretResponse(serverResponseIcon); + expect(result).to.be.an('array').with.lengthOf(1); + const native = result[0].native; + + // icon mapped from id 13 + expect(native.icon).to.equal('https://img.example/icon.png'); + expect(native.icon_width).to.equal(50); + expect(native.icon_height).to.equal(50); + + // image mapped from id 14 + expect(native.image).to.equal('https://img.example/img.png'); + expect(native.image_width).to.equal(200); + expect(native.image_height).to.equal(100); + }); + }); }); From 315e789cf5cac4e5163902df27f3c5e9a8dbf008 Mon Sep 17 00:00:00 2001 From: Alexandr Kim <47887567+alexandr-kim-vl@users.noreply.github.com> Date: Wed, 11 Feb 2026 22:21:05 +0500 Subject: [PATCH 041/105] Yaleo Bid Adapter: initial release (#14452) Co-authored-by: Alexandr Kim --- modules/yaleoBidAdapter.md | 39 ++++ modules/yaleoBidAdapter.ts | 80 ++++++++ test/spec/modules/yaleoBidAdapter_spec.js | 213 ++++++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 modules/yaleoBidAdapter.md create mode 100755 modules/yaleoBidAdapter.ts create mode 100644 test/spec/modules/yaleoBidAdapter_spec.js diff --git a/modules/yaleoBidAdapter.md b/modules/yaleoBidAdapter.md new file mode 100644 index 00000000000..505d3617fd5 --- /dev/null +++ b/modules/yaleoBidAdapter.md @@ -0,0 +1,39 @@ +# Yaleo Bid Adapter + +# Overview + +``` +Module name: Yaleo Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandr.kim@audienzz.com +``` + +# Description + +Module that connects to Yaleo's demand sources. + +**Note:** the bid adapter requires correct setup and approval. For more information visit [yaleo.com](https://www.yaleo.com) or contact [hola@yaleo.com](mailto:hola@yaleo.com). + +# Test parameters + +**Note:** to receive bids when testing without proper integration with the demand source, enable Prebid.js debug mode. See [how to enable debug mode](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#debugging) for details. + +```js +const adUnits = [ + { + code: "test-div-1", + mediaTypes: { + banner: { + sizes: [[300, 300], [300, 600]], + } + }, + bids: [{ + bidder: "yaleo", + params: { + placementId: "95a09f24-afb8-441c-977b-08b4039cb88e", + } + }] + } +]; +``` + diff --git a/modules/yaleoBidAdapter.ts b/modules/yaleoBidAdapter.ts new file mode 100755 index 00000000000..980d5634df7 --- /dev/null +++ b/modules/yaleoBidAdapter.ts @@ -0,0 +1,80 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; +import { BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +interface YaleoBidParams { + /** + * Yaleo placement ID. + */ + placementId: string; + /** + * Member ID. + * @default 3927 + */ + memberId?: number; + /** + * Maximum CPM value. Bids with a CPM higher than the specified value will be rejected. + */ + maxCpm?: number; +} + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: YaleoBidParams; + } +} + +const BIDDER_CODE = 'yaleo'; +const AUDIENZZ_VENDOR_ID = 783; +const PREBID_URL = 'https://bidder.yaleo.com/prebid'; +const DEFAULT_TTL = 300; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_TTL, + }, + processors: pbsExtensions, +}); + +const isBidRequestValid: BidderSpec['isBidRequestValid'] = (request) => { + if (!request.params || typeof request.params.placementId !== 'string') { + return false; + } + + return !!request.params.placementId; +}; + +const buildRequests: BidderSpec['buildRequests'] = (validBidRequests, bidderRequest) => { + const ortbRequest = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + }); + + return { + url: PREBID_URL, + method: 'POST', + data: ortbRequest, + }; +} + +const interpretResponse: BidderSpec['interpretResponse'] = (serverResponse, bidderRequest) => { + const response = converter.fromORTB({ + response: serverResponse.body, + request: bidderRequest.data, + }); + + return response; +}; + +export const spec: BidderSpec = { + buildRequests, + code: BIDDER_CODE, + gvlid: AUDIENZZ_VENDOR_ID, + interpretResponse, + isBidRequestValid, + supportedMediaTypes: [BANNER], +}; + +registerBidder(spec); diff --git a/test/spec/modules/yaleoBidAdapter_spec.js b/test/spec/modules/yaleoBidAdapter_spec.js new file mode 100644 index 00000000000..5bbba97109c --- /dev/null +++ b/test/spec/modules/yaleoBidAdapter_spec.js @@ -0,0 +1,213 @@ +import { cloneDeep } from "lodash"; +import { spec } from "../../../modules/yaleoBidAdapter.ts"; + +const bannerBidRequestBase = { + adUnitCode: 'banner-ad-unit-code', + auctionId: 'banner-auction-id', + bidId: 'banner-bid-id', + bidder: 'yaleo', + bidderRequestId: 'banner-bidder-request-id', + mediaTypes: { banner: [[300, 250]] }, + params: { placementId: '12345' }, +}; + +const bannerServerResponseBase = { + body: { + id: 'banner-server-response-id', + seatbid: [ + { + bid: [ + { + id: 'banner-seatbid-bid-id', + impid: 'banner-bid-id', + price: 0.05, + adm: '
banner-ad
', + adid: 'banner-ad-id', + adomain: ['audienzz.com', 'yaleo.com'], + iurl: 'iurl', + cid: 'banner-campaign-id', + crid: 'banner-creative-id', + cat: [], + w: 300, + h: 250, + mtype: 1, + }, + ], + seat: 'yaleo', + }, + ], + cur: 'USD', + }, +}; + +describe('Yaleo bid adapter', () => { + let bannerBidRequest; + + beforeEach(() => { + bannerBidRequest = cloneDeep(bannerBidRequestBase); + }); + + describe('spec', () => { + it('checks that spec has required properties', () => { + expect(spec).to.have.property('code', 'yaleo'); + expect(spec).to.have.property('supportedMediaTypes').that.includes('banner'); + expect(spec).to.have.property('isBidRequestValid').that.is.a('function'); + expect(spec).to.have.property('buildRequests').that.is.a('function'); + expect(spec).to.have.property('interpretResponse').that.is.a('function'); + expect(spec).to.have.property('gvlid').that.equals(783); + }); + }); + + describe('isBidRequestValid', () => { + it('returns true when all params are specified', () => { + bannerBidRequest.params = { + placementId: '12345', + maxCpm: 5.00, + memberId: 12345, + }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.true; + }); + + it('returns true when required params are specified', () => { + bannerBidRequest.params = { placementId: '12345' }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.true; + }); + + it('returns false when params are empty', () => { + bannerBidRequest.params = {}; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when params are not specified', () => { + bannerBidRequest.params = undefined; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returnsfalse when required params are not specified', () => { + bannerBidRequest.params = { wrongParam: '12345' }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is a number', () => { + bannerBidRequest.params = { placementId: 12345 }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is a boolean', () => { + bannerBidRequest.params = { placementId: true }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is an object', () => { + bannerBidRequest.params = { placementId: {} }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is an array', () => { + bannerBidRequest.params = { placementId: [] }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is undefined', () => { + bannerBidRequest.params = { placementId: undefined }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + + it('returns false when placementId is null', () => { + bannerBidRequest.params = { placementId: null }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + }); + }); + + describe('buildRequests', () => { + it('creates a valid banner bid request', () => { + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + ortb2: { + site: { + page: 'http://example.com', + } + } + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bidder.yaleo.com/prebid'); + expect(request.data.imp.length).to.equal(1); + expect(request.data.imp[0]).to.have.property('banner'); + expect(request.data.site.page).to.be.equal('http://example.com'); + }); + + it('checks that all params are passed to the request', () => { + bannerBidRequest.params = { + placementId: '12345', + maxCpm: 5.00, + memberId: 12345, + }; + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const yaleoParams = request.data.imp[0].ext.prebid.bidder.yaleo; + + expect(yaleoParams.placementId).to.equal('12345'); + expect(yaleoParams.maxCpm).to.equal(5.00); + expect(yaleoParams.memberId).to.equal(12345); + }); + }); + + it('checks that only specified params are passed to the request', () => { + bannerBidRequest.params = { + placementId: '12345', + }; + + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const yaleoParams = request.data.imp[0].ext.prebid.bidder.yaleo; + + expect(yaleoParams).to.deep.equal({ placementId: '12345' }); + }); + + describe('interpretResponse', () => { + let bannerServerResponse; + beforeEach(() => { + bannerServerResponse = cloneDeep(bannerServerResponseBase); + }); + + it('parses banner bid response correctly', () => { + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const response = spec.interpretResponse(bannerServerResponse, request); + + expect(response).to.have.property('bids'); + expect(response.bids.length).to.be.equal(1); + expect(response.bids[0].cpm).to.equal(0.05); + expect(response.bids[0].currency).to.equal('USD'); + expect(response.bids[0].mediaType).to.equal('banner'); + expect(response.bids[0].width).to.equal(300); + expect(response.bids[0].height).to.equal(250); + expect(response.bids[0].creativeId).to.equal('banner-creative-id'); + expect(response.bids[0].ad).to.equal('
banner-ad
'); + expect(response.bids[0].bidderCode).to.equal('yaleo'); + expect(response.bids[0].meta.advertiserDomains).to.deep.equal(['audienzz.com', 'yaleo.com']); + expect(response.bids[0].netRevenue).to.be.true; + expect(response.bids[0].ttl).to.equal(300); + }); + }); +}); From 4273a96b6d22842564244bca289199afa15cc457 Mon Sep 17 00:00:00 2001 From: Shashank Pradeep <101392500+shashankatd@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:03:28 +0530 Subject: [PATCH 042/105] feat: Mile Bid Adapter - Initial release (#14388) Co-authored-by: Shashank <=> --- modules/mileBidAdapter.md | 72 +++ modules/mileBidAdapter.ts | 428 ++++++++++++++ test/spec/modules/mileBidAdapter_spec.js | 691 +++++++++++++++++++++++ 3 files changed, 1191 insertions(+) create mode 100644 modules/mileBidAdapter.md create mode 100644 modules/mileBidAdapter.ts create mode 100644 test/spec/modules/mileBidAdapter_spec.js diff --git a/modules/mileBidAdapter.md b/modules/mileBidAdapter.md new file mode 100644 index 00000000000..0124c5f4327 --- /dev/null +++ b/modules/mileBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: Mile Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@mile.tech +``` + +# Description + +This bidder adapter connects to Mile demand sources. + +# Bid Params + +| Name | Scope | Description | Example | Type | +|---------------|----------|-----------------------------------|------------------|--------| +| `placementId` | required | The placement ID for the ad unit | `'12345'` | string | +| `siteId` | required | The site ID for the publisher | `'site123'` | string | +| `publisherId` | required | The publisher ID | `'pub456'` | string | + +# Example Configuration + +```javascript +var adUnits = [{ + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'mile', + params: { + placementId: 'test-placement', + siteId: 'test-site', + publisherId: 'test-pub' + } + }] +}]; +``` + +# User Sync Configuration + +To enable user syncing, configure Prebid.js with: + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, // Enable iframe syncs + filterSettings: { + iframe: { + bidders: ['mile'], + filter: 'include' + } + } + } +}); +``` + +# Test Parameters + +```javascript +{ + bidder: 'mile', + params: { + placementId: 'test-placement', + siteId: 'test-site', + publisherId: 'test-publisher-id' + } +} +``` + diff --git a/modules/mileBidAdapter.ts b/modules/mileBidAdapter.ts new file mode 100644 index 00000000000..f24feb8b74b --- /dev/null +++ b/modules/mileBidAdapter.ts @@ -0,0 +1,428 @@ +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'; + +/** + * Mile Bid Adapter + * + * This adapter handles: + * 1. User syncs by sending requests to the prebid server cookie sync endpoint + * 2. Bid requests by parsing necessary parameters from the prebid auction + */ + +const BIDDER_CODE = 'mile'; + +const MILE_BIDDER_HOST = 'https://pbs.atmtd.com'; +const ENDPOINT_URL = `${MILE_BIDDER_HOST}/mile/v1/request`; +const USER_SYNC_ENDPOINT = `https://scripts.atmtd.com/user-sync/load-cookie.html`; + +const MILE_ANALYTICS_ENDPOINT = `https://e01.atmtd.com/bidanalytics-event/json`; + +type MileBidParams = { + placementId: string; + siteId: string; + publisherId: string; +}; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: MileBidParams; + } +} + +export let siteIdTracker : string | undefined; +export let publisherIdTracker : string | undefined; + +export function getLowestFloorPrice(bid) { + let floorPrice: number; + + if (typeof bid.getFloor === 'function') { + let sizes = [] + // Get floor prices for each banner size in the bid request + if (deepAccess(bid, 'mediaTypes.banner.sizes')) { + sizes = deepAccess(bid, 'mediaTypes.banner.sizes') + } else if (bid.sizes) { + sizes = bid.sizes + } + + sizes.forEach((size: string | number[]) => { + const [w, h] = typeof size === 'string' ? size.split('x') : size as number[]; + const floor = bid.getFloor({ currency: 'USD', mediaType: '*', size: [Number(w), Number(h)] }); + if (floor && floor.floor) { + if (floorPrice === undefined) { + floorPrice = floor.floor; + } else { + floorPrice = Math.min(floorPrice, floor.floor); + } + } + }); + } else { + floorPrice = 0 + } + + return floorPrice +} + +export const spec: BidderSpec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + const params = bid.params; + + if (!params?.placementId) { + logError(`${BIDDER_CODE}: Missing required param: placementId`); + return false; + } + + if (!params?.siteId) { + logError(`${BIDDER_CODE}: Missing required param: siteId`); + return false; + } + + if (!params?.publisherId) { + logError(`${BIDDER_CODE}: Missing required param: publisherId`); + return false; + } + + if (siteIdTracker === undefined) { + siteIdTracker = params.siteId; + } else if (siteIdTracker !== params.siteId) { + logError(`${BIDDER_CODE}: Site ID mismatch: ${siteIdTracker} !== ${params.siteId}`); + return false; + } + + if (publisherIdTracker === undefined) { + publisherIdTracker = params.publisherId; + } else if (publisherIdTracker !== params.publisherId) { + logError(`${BIDDER_CODE}: Publisher ID mismatch: ${publisherIdTracker} !== ${params.publisherId}`); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * Builds an OpenRTB 2.5 compliant bid request. + * + * @param validBidRequests - An array of valid bids + * @param bidderRequest - The master bidder request object + * @returns ServerRequest info describing the request to the server + */ + buildRequests: function (validBidRequests, bidderRequest) { + logInfo(`${BIDDER_CODE}: Building batched request for ${validBidRequests.length} bids`); + + // Build imp[] array - one impression object per bid request + const imps = validBidRequests.map((bid) => { + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') || []; + const floorPrice = getLowestFloorPrice(bid); + + const imp: any = { + id: bid.bidId, + tagid: bid.params.placementId, + secure: 1, + banner: { + format: sizes.map((size: number[]) => ({ + w: size[0], + h: size[1], + })) + }, + ext: { + adUnitCode: bid.adUnitCode, + placementId: bid.params.placementId, + gpid: deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), + }, + }; + + // Add bidfloor if available + if (floorPrice > 0) { + imp.bidfloor = floorPrice; + imp.bidfloorcur = 'USD'; + } + + return imp; + }); + + // Build the OpenRTB 2.5 BidRequest object + const openRtbRequest: any = { + id: bidderRequest.bidderRequestId || generateUUID(), + imp: imps, + tmax: bidderRequest.timeout, + cur: ['USD'], + site: { + id: siteIdTracker, + page: deepAccess(bidderRequest, 'refererInfo.page') || '', + domain: deepAccess(bidderRequest, 'refererInfo.domain') || '', + ref: deepAccess(bidderRequest, 'refererInfo.ref') || '', + publisher: { + id: publisherIdTracker, + }, + }, + user: {}, + // Device object + device: { + ua: navigator.userAgent, + language: navigator.language?.split('-')[0] || 'en', + dnt: getDNT() ? 1 : 0, + w: window.screen?.width, + h: window.screen?.height, + }, + + // Source object with supply chain + source: { + tid: deepAccess(bidderRequest, 'ortb2.source.tid') + }, + }; + + // Add schain if available + const schain = deepAccess(validBidRequests, '0.ortb2.source.ext.schain'); + if (schain) { + deepSetValue(openRtbRequest, 'source.ext.schain', schain); + } + + // User object + const eids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (eids && eids.length) { + deepSetValue(openRtbRequest, 'user.ext.eids', eids); + } + + // Regs object for privacy/consent + const regs: any = { ext: {} }; + + // GDPR + if (bidderRequest.gdprConsent) { + regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + deepSetValue(openRtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString || ''); + } + + // US Privacy (CCPA) + if (bidderRequest.uspConsent) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // GPP + if (bidderRequest.gppConsent) { + regs.gpp = bidderRequest.gppConsent.gppString || ''; + regs.gpp_sid = bidderRequest.gppConsent.applicableSections || []; + } + + if (Object.keys(regs.ext).length > 0 || regs.gpp) { + openRtbRequest.regs = regs; + } + + // Merge any first-party data from ortb2 + if (bidderRequest.ortb2) { + if (bidderRequest.ortb2.site) { + openRtbRequest.site = { ...openRtbRequest.site, ...bidderRequest.ortb2.site }; + // Preserve publisher ID + openRtbRequest.site.publisher = { id: publisherIdTracker, ...bidderRequest.ortb2.site.publisher }; + } + if (bidderRequest.ortb2.user) { + openRtbRequest.user = { ...openRtbRequest.user, ...bidderRequest.ortb2.user }; + } + if (bidderRequest.ortb2.device) { + openRtbRequest.device = { ...openRtbRequest.device, ...bidderRequest.ortb2.device }; + } + } + + // Add prebid adapter version info + deepSetValue(openRtbRequest, 'ext.prebid.channel', { + name: 'pbjs', + version: '$prebid.version$', + }); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: openRtbRequest + }; + }, + + /** + * Unpack the OpenRTB 2.5 response from the server into a list of bids. + * + * @param serverResponse - A successful response from the server. + * @param request - The request that was sent to the server. + * @returns An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + const bidResponses: any[] = []; + + if (!serverResponse?.body) { + logInfo(`${BIDDER_CODE}: Empty server response`); + return bidResponses; + } + + const response = serverResponse.body; + const currency = response.cur || 'USD'; + + // OpenRTB 2.5 response format: seatbid[] contains bid[] + const seatbids = response.bids || []; + + seatbids.forEach((bid: any) => { + if (!bid || !bid.cpm) { + return; + } + + const bidResponse = { + requestId: bid.requestId, + cpm: parseFloat(bid.cpm), + width: parseInt(bid.width || bid.w, 10), + height: parseInt(bid.height || bid.h, 10), + creativeId: bid.creativeId || bid.crid || bid.id, + currency: currency, + netRevenue: true, + bidder: BIDDER_CODE, + ttl: bid.ttl || 300, + ad: bid.ad, + mediaType: BANNER, + meta: { + advertiserDomains: bid.adomain || [], + upstreamBidder: bid.upstreamBidder || '', + siteUID: deepAccess(response, 'site.id') || '', + publisherID: deepAccess(response, 'site.publisher.id') || '', + page: deepAccess(response, 'site.page') || '', + domain: deepAccess(response, 'site.domain') || '', + } + }; + + // Handle nurl (win notice URL) if present + if (bid.nurl) { + (bidResponse as any).nurl = bid.nurl; + } + + // Handle burl (billing URL) if present + if (bid.burl) { + (bidResponse as any).burl = bid.burl; + } + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + /** + * Register user sync pixels or iframes to be dropped after the auction. + * + * @param syncOptions - Which sync types are allowed (iframe, image/pixel) + * @param serverResponses - Array of server responses from the auction + * @param gdprConsent - GDPR consent data + * @param uspConsent - US Privacy consent string + * @param gppConsent - GPP consent data + * @returns Array of user sync objects to be executed + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent, + gppConsent + ) { + logInfo(`${BIDDER_CODE}: getUserSyncs called`, { + iframeEnabled: syncOptions.iframeEnabled + }); + + const syncs = []; + + // Build query parameters for consent + const queryParams: string[] = []; + + // GDPR consent + if (gdprConsent) { + queryParams.push(`gdpr=${gdprConsent.gdprApplies ? 1 : 0}`); + queryParams.push(`gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`); + } + + // US Privacy / CCPA + if (uspConsent) { + queryParams.push(`us_privacy=${encodeURIComponent(uspConsent)}`); + } + + // GPP consent + if (gppConsent?.gppString) { + queryParams.push(`gpp=${encodeURIComponent(gppConsent.gppString)}`); + if (gppConsent.applicableSections?.length) { + queryParams.push(`gpp_sid=${encodeURIComponent(gppConsent.applicableSections.join(','))}`); + } + } + + const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : ''; + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe' as const, + url: `${USER_SYNC_ENDPOINT}${queryString}`, + }); + } + + return syncs; + }, + + /** + * Called when a bid from this adapter wins the auction. + * Sends an XHR POST request to the bid's nurl (win notification URL). + * + * @param bid - The winning bid object + */ + onBidWon: function (bid) { + logInfo(`${BIDDER_CODE}: Bid won`, bid); + + const winNotificationData = { + adUnitCode: bid.adUnitCode, + metaData: { + impressionID: [bid.requestId], + }, + ua: navigator.userAgent, + timestamp: Date.now(), + winningSize: `${bid.width}x${bid.height}`, + cpm: bid.cpm, + eventType: 'mile-bidder-win-notify', + winningBidder: deepAccess(bid, 'meta.upstreamBidder') || '', + siteUID: deepAccess(bid, 'meta.siteUID') || '', + yetiPublisherID: deepAccess(bid, 'meta.publisherID') || '', + page: deepAccess(bid, 'meta.page') || '', + site: deepAccess(bid, 'meta.domain') || '', + } + + ajax(MILE_ANALYTICS_ENDPOINT, null, JSON.stringify([winNotificationData]), { method: 'POST'}); + + // @ts-expect-error - bid.nurl is not defined + if (bid.nurl) ajax(bid.nurl, null, null, { method: 'GET' }); + }, + + /** + * Called when bid requests timeout. + * Sends analytics notification for timed out bids. + * + * @param timeoutData - Array of bid requests that timed out + */ + onTimeout: function (timeoutData) { + logInfo(`${BIDDER_CODE}: Timeout for ${timeoutData.length} bid(s)`, timeoutData); + + if (timeoutData.length === 0) return; + + const timedOutBids = []; + + timeoutData.forEach((bid) => { + const timeoutNotificationData = { + adUnitCode: bid.adUnitCode, + metaData: { + impressionID: [bid.bidId], + configuredTimeout: [bid.timeout.toString()], + }, + ua: navigator.userAgent, + timestamp: Date.now(), + eventType: 'mile-bidder-timeout' + }; + + timedOutBids.push(timeoutNotificationData); + }); + + ajax(MILE_ANALYTICS_ENDPOINT, null, JSON.stringify(timedOutBids), { method: 'POST'}); + }, +}; + +registerBidder(spec); diff --git a/test/spec/modules/mileBidAdapter_spec.js b/test/spec/modules/mileBidAdapter_spec.js new file mode 100644 index 00000000000..34171f86114 --- /dev/null +++ b/test/spec/modules/mileBidAdapter_spec.js @@ -0,0 +1,691 @@ +import { expect } from 'chai'; +import { spec, siteIdTracker, publisherIdTracker } from 'modules/mileBidAdapter.js'; +import { BANNER } from 'src/mediaTypes.js'; +import * as ajax from 'src/ajax.js'; +import * as utils from 'src/utils.js'; + +describe('mileBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid; + + beforeEach(function () { + bid = { + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + }); + + it('should return true when all required params are present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when placementId is missing', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when siteId is missing', function () { + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when publisherId is missing', function () { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when params is missing', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when params is null', function () { + bid.params = null; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let validBidRequests, bidderRequest; + + beforeEach(function () { + validBidRequests = [{ + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + adUnitCode: 'test-ad-unit', + bidId: 'bid123', + ortb2Imp: { + ext: { + gpid: '/test/ad/unit' + } + } + }]; + + bidderRequest = { + bidderCode: 'mile', + bidderRequestId: 'bidderReq123', + auctionId: 'auction123', + timeout: 3000, + refererInfo: { + page: 'https://example.com/page', + domain: 'example.com', + ref: 'https://google.com' + }, + ortb2: { + source: { + tid: 'transaction123' + } + } + }; + }); + + it('should return a valid server request object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://pbs.atmtd.com/mile/v1/request'); + expect(request.data).to.be.an('object'); + }); + + it('should build OpenRTB 2.5 compliant request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = request.data; + + expect(data.id).to.equal('bidderReq123'); + expect(data.imp).to.be.an('array').with.lengthOf(1); + expect(data.tmax).to.equal(3000); + expect(data.cur).to.deep.equal(['USD']); + expect(data.site).to.be.an('object'); + expect(data.device).to.be.an('object'); + expect(data.source).to.be.an('object'); + }); + + it('should include imp object with correct structure', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const imp = request.data.imp[0]; + + expect(imp.id).to.equal('bid123'); + expect(imp.tagid).to.equal('12345'); + expect(imp.secure).to.equal(1); + expect(imp.banner).to.be.an('object'); + expect(imp.banner.format).to.be.an('array').with.lengthOf(2); + expect(imp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); + expect(imp.banner.format[1]).to.deep.equal({ w: 728, h: 90 }); + }); + + it('should include ext fields in imp object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const imp = request.data.imp[0]; + + expect(imp.ext.adUnitCode).to.equal('test-ad-unit'); + expect(imp.ext.placementId).to.equal('12345'); + expect(imp.ext.gpid).to.equal('/test/ad/unit'); + }); + + it('should include site object with publisher info', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const site = request.data.site; + + expect(site.id).to.equal('site123'); + expect(site.page).to.equal('https://example.com/page'); + expect(site.domain).to.equal('example.com'); + expect(site.ref).to.equal('https://google.com'); + expect(site.publisher.id).to.equal('pub456'); + }); + + it('should include device object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const device = request.data.device; + + expect(device.ua).to.be.a('string'); + expect(device.language).to.be.a('string'); + expect(device.dnt).to.be.a('number'); + }); + + it('should include source object with tid', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const source = request.data.source; + + expect(source.tid).to.equal('transaction123'); + }); + + it('should include bidfloor when floor price is available', function () { + validBidRequests[0].getFloor = function() { + return { floor: 0.5, currency: 'USD' }; + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const imp = request.data.imp[0]; + + expect(imp.bidfloor).to.equal(0.5); + expect(imp.bidfloorcur).to.equal('USD'); + }); + + it('should include GDPR consent when present', function () { + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent-string-123' + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.user.ext.consent).to.equal('consent-string-123'); + }); + + it('should include US Privacy consent when present', function () { + bidderRequest.uspConsent = '1YNN'; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.regs.ext.us_privacy).to.equal('1YNN'); + }); + + it('should include GPP consent when present', function () { + bidderRequest.gppConsent = { + gppString: 'gpp-string-123', + applicableSections: [1, 2, 3] + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.regs.gpp).to.equal('gpp-string-123'); + expect(request.data.regs.gpp_sid).to.deep.equal([1, 2, 3]); + }); + + it('should include user EIDs when present', function () { + validBidRequests[0].userIdAsEids = [ + { + source: 'pubcid.org', + uids: [{ id: 'user-id-123' }] + } + ]; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.user.ext.eids).to.be.an('array').with.lengthOf(1); + expect(request.data.user.ext.eids[0].source).to.equal('pubcid.org'); + }); + + it('should include supply chain when present', function () { + validBidRequests[0].ortb2 = { + source: { + ext: { + schain: { + ver: '1.0', + complete: 1, + nodes: [] + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.source.ext.schain).to.be.an('object'); + expect(request.data.source.ext.schain.ver).to.equal('1.0'); + }); + + it('should handle multiple bid requests with same siteId and publisherId', function () { + const secondBid = { + ...validBidRequests[0], + bidId: 'bid456', + params: { + placementId: '67890', + siteId: 'site123', + publisherId: 'pub456' + } + }; + + const request = spec.buildRequests([validBidRequests[0], secondBid], bidderRequest); + + expect(request.data.imp).to.be.an('array').with.lengthOf(2); + expect(request.data.imp[0].id).to.equal('bid123'); + expect(request.data.imp[1].id).to.equal('bid456'); + }); + + it('should reject bids with different siteId', function () { + const firstBid = { + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const secondBid = { + bidder: 'mile', + params: { + placementId: '67890', + siteId: 'differentSite', // Different siteId + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + // First bid should be valid + expect(spec.isBidRequestValid(firstBid)).to.be.true; + + // Second bid should be rejected due to siteId mismatch + expect(spec.isBidRequestValid(secondBid)).to.be.false; + }); + + it('should reject bids with different publisherId', function () { + const firstBid = { + bidder: 'mile', + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const secondBid = { + bidder: 'mile', + params: { + placementId: '67890', + siteId: 'site123', + publisherId: 'differentPub' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + // First bid should be valid + expect(spec.isBidRequestValid(firstBid)).to.be.true; + + // Second bid should be rejected due to publisherId mismatch + expect(spec.isBidRequestValid(secondBid)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + let serverResponse; + + beforeEach(function () { + serverResponse = { + body: { + site: { + id: 'site123', + domain: 'example.com', + publisher: { + id: 'pub456' + }, + page: 'https://example.com/page', + }, + cur: 'USD', + bids: [ + { + requestId: 'bid123', + cpm: 1.5, + width: 300, + height: 250, + ad: '
test ad
', + creativeId: 'creative123', + ttl: 300, + nurl: 'https://example.com/win?price=${AUCTION_PRICE}', + adomain: ['advertiser.com'], + upstreamBidder: 'upstreamBidder' + } + ] + } + }; + }); + + it('should return an array of bid responses', function () { + const bids = spec.interpretResponse(serverResponse); + + expect(bids).to.be.an('array').with.lengthOf(1); + }); + + it('should parse bid response correctly', function () { + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.requestId).to.equal('bid123'); + expect(bid.cpm).to.equal(1.5); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.equal('
test ad
'); + expect(bid.creativeId).to.equal('creative123'); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.netRevenue).to.be.true; + expect(bid.mediaType).to.equal(BANNER); + expect(bid.meta.upstreamBidder).to.equal('upstreamBidder'); + expect(bid.meta.siteUID).to.equal('site123'); + expect(bid.meta.publisherID).to.equal('pub456'); + expect(bid.meta.page).to.equal('https://example.com/page'); + expect(bid.meta.domain).to.equal('example.com'); + }); + + it('should include nurl in bid response', function () { + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.nurl).to.equal('https://example.com/win?price=${AUCTION_PRICE}'); + }); + + it('should include meta.advertiserDomains', function () { + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + }); + + it('should handle empty response', function () { + const bids = spec.interpretResponse({ body: null }); + + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should handle response with no bids', function () { + serverResponse.body.bids = []; + const bids = spec.interpretResponse(serverResponse); + + expect(bids).to.be.an('array').with.lengthOf(0); + }); + + it('should handle alternative field names (w/h instead of width/height)', function () { + serverResponse.body.bids[0] = { + requestId: 'bid123', + cpm: 1.5, + w: 728, + h: 90, + ad: '
test ad
' + }; + + const bids = spec.interpretResponse(serverResponse); + const bid = bids[0]; + + expect(bid.width).to.equal(728); + expect(bid.height).to.equal(90); + }); + + it('should use default currency if not specified', function () { + delete serverResponse.body.cur; + const bids = spec.interpretResponse(serverResponse); + + expect(bids[0].currency).to.equal('USD'); + }); + + it('should handle response with no site or publisher', function () { + delete serverResponse.body.site; + delete serverResponse.body.publisher; + const bids = spec.interpretResponse(serverResponse); + + expect(bids[0].meta.siteUID).to.be.empty; + expect(bids[0].meta.publisherID).to.be.empty; + expect(bids[0].meta.page).to.be.empty; + expect(bids[0].meta.domain).to.be.empty; + }); + }); + + describe('getUserSyncs', function () { + let syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent; + + beforeEach(function () { + syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + serverResponses = []; + }); + + it('should return iframe sync when enabled', function () { + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include('https://scripts.atmtd.com/user-sync/load-cookie.html'); + }); + + it('should not return syncs when iframe is disabled', function () { + syncOptions.iframeEnabled = false; + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.be.an('array').with.lengthOf(0); + }); + + it('should include GDPR consent params', function () { + gdprConsent = { + gdprApplies: true, + consentString: 'consent-string-123' + }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=consent-string-123'); + }); + + it('should include US Privacy consent param', function () { + uspConsent = '1YNN'; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, null, uspConsent); + + expect(syncs[0].url).to.include('us_privacy=1YNN'); + }); + + it('should include GPP consent params', function () { + gppConsent = { + gppString: 'gpp-string-123', + applicableSections: [1, 2, 3] + }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, null, null, gppConsent); + + expect(syncs[0].url).to.include('gpp=gpp-string-123'); + expect(syncs[0].url).to.include('gpp_sid=1%2C2%2C3'); + }); + + it('should include all consent params when present', function () { + gdprConsent = { gdprApplies: true, consentString: 'gdpr-consent' }; + uspConsent = '1YNN'; + gppConsent = { gppString: 'gpp-string', applicableSections: [1] }; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('us_privacy=1YNN'); + expect(syncs[0].url).to.include('gpp=gpp-string'); + }); + }); + + describe('onBidWon', function () { + let bid, ajaxStub; + + beforeEach(function () { + bid = { + bidder: 'mile', + adUnitCode: 'test-ad-unit', + requestId: 'bid123', + cpm: 1.5, + width: 300, + height: 250, + nurl: 'https://example.com/win', + meta: { + upstreamBidder: 'upstreamBidder', + siteUID: 'mRUDIL', + publisherID: 'pub456', + page: 'https://example.com/page', + domain: 'example.com' + } + }; + + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should call ajax with win notification endpoint', function () { + spec.onBidWon(bid); + + expect(ajaxStub.calledTwice).to.be.true; + + // First call to notification endpoint + const firstCall = ajaxStub.getCall(0); + expect(firstCall.args[0]).to.equal('https://e01.atmtd.com/bidanalytics-event/json'); + + // Second call to nurl + const secondCall = ajaxStub.getCall(1); + expect(secondCall.args[0]).to.equal('https://example.com/win'); + }); + + it('should send correct win notification data', function () { + spec.onBidWon(bid); + + const firstCall = ajaxStub.getCall(0); + const notificationData = JSON.parse(firstCall.args[2])[0]; + + expect(notificationData.adUnitCode).to.equal('test-ad-unit'); + expect(notificationData.metaData.impressionID[0]).to.equal('bid123'); + expect(notificationData.winningBidder).to.equal('upstreamBidder'); + expect(notificationData.cpm).to.equal(1.5); + expect(notificationData.winningSize).to.equal('300x250'); + expect(notificationData.eventType).to.equal('mile-bidder-win-notify'); + expect(notificationData.timestamp).to.be.a('number'); + expect(notificationData.siteUID).to.equal('mRUDIL'); + expect(notificationData.yetiPublisherID).to.equal('pub456'); + expect(notificationData.page).to.equal('https://example.com/page'); + expect(notificationData.site).to.equal('example.com'); + }); + + it('should call nurl with GET request', function () { + spec.onBidWon(bid); + + const secondCall = ajaxStub.getCall(1); + const options = secondCall.args[3]; + + expect(options.method).to.equal('GET'); + }); + }); + + describe('onTimeout', function () { + let timeoutData, ajaxStub; + + beforeEach(function () { + timeoutData = [ + { + bidder: 'mile', + bidId: 'bid123', + adUnitCode: 'test-ad-unit-1', + timeout: 3000, + params: { + placementId: '12345', + siteId: 'site123', + publisherId: 'pub456' + } + }, + { + bidder: 'mile', + bidId: 'bid456', + adUnitCode: 'test-ad-unit-2', + timeout: 3000, + params: { + placementId: '67890', + siteId: 'site123', + publisherId: 'pub456' + } + } + ]; + + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should call ajax for each timed out bid', function () { + spec.onTimeout(timeoutData); + + expect(ajaxStub.callCount).to.equal(1); + }); + + it('should send correct timeout notification data', function () { + spec.onTimeout(timeoutData); + + const firstCall = ajaxStub.getCall(0); + expect(firstCall.args[0]).to.equal('https://e01.atmtd.com/bidanalytics-event/json'); + + const notificationData = JSON.parse(firstCall.args[2])[0]; + expect(notificationData.adUnitCode).to.equal('test-ad-unit-1'); + expect(notificationData.metaData.impressionID[0]).to.equal('bid123'); + expect(notificationData.metaData.configuredTimeout[0]).to.equal('3000'); + expect(notificationData.eventType).to.equal('mile-bidder-timeout'); + expect(notificationData.timestamp).to.be.a('number'); + }); + + it('should handle single timeout', function () { + spec.onTimeout([timeoutData[0]]); + + expect(ajaxStub.calledOnce).to.be.true; + }); + + it('should handle empty timeout array', function () { + spec.onTimeout([]); + + expect(ajaxStub.called).to.be.false; + }); + }); + + describe('adapter specification', function () { + it('should have correct bidder code', function () { + expect(spec.code).to.equal('mile'); + }); + + it('should support BANNER media type', function () { + expect(spec.supportedMediaTypes).to.be.an('array'); + expect(spec.supportedMediaTypes).to.include(BANNER); + }); + + it('should have required adapter functions', function () { + expect(spec.isBidRequestValid).to.be.a('function'); + expect(spec.buildRequests).to.be.a('function'); + expect(spec.interpretResponse).to.be.a('function'); + expect(spec.getUserSyncs).to.be.a('function'); + expect(spec.onBidWon).to.be.a('function'); + expect(spec.onTimeout).to.be.a('function'); + }); + }); +}); From 3326f76d321164530cdc1677aa701c96f491a0f8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 11 Feb 2026 13:47:18 -0800 Subject: [PATCH 043/105] bidMatic bid adapter: update placement info logic (#14418) --- .../placementPositionInfo.js | 78 +---- .../libraries/placementPositionInfo_spec.js | 315 +----------------- 2 files changed, 28 insertions(+), 365 deletions(-) diff --git a/libraries/placementPositionInfo/placementPositionInfo.js b/libraries/placementPositionInfo/placementPositionInfo.js index b5bdf47fb2d..19014632a23 100644 --- a/libraries/placementPositionInfo/placementPositionInfo.js +++ b/libraries/placementPositionInfo/placementPositionInfo.js @@ -1,74 +1,20 @@ import {getBoundingClientRect} from '../boundingClientRect/boundingClientRect.js'; -import {canAccessWindowTop, cleanObj, getWindowSelf, getWindowTop} from '../../src/utils.js'; -import {getViewability} from '../percentInView/percentInView.js'; +import {canAccessWindowTop, cleanObj, getWinDimensions, getWindowSelf, getWindowTop} from '../../src/utils.js'; +import {getViewability, getViewportOffset} from '../percentInView/percentInView.js'; export function getPlacementPositionUtils() { const topWin = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); const selfWin = getWindowSelf(); - const findElementWithContext = (adUnitCode) => { - let element = selfWin.document.getElementById(adUnitCode); - if (element) { - return {element, frameOffset: getFrameOffsetForCurrentWindow()}; - } - - const searchInIframes = (doc, accumulatedOffset = {top: 0}, iframeWindow = null) => { - try { - const element = doc.getElementById(adUnitCode); - if (element) { - return {element, frameOffset: accumulatedOffset, iframeWindow}; - } - - const frames = doc.getElementsByTagName('iframe'); - for (const frame of frames) { - try { - const iframeDoc = frame.contentDocument || frame.contentWindow?.document; - if (iframeDoc) { - const frameRect = getBoundingClientRect(frame); - const newOffset = { - top: accumulatedOffset.top + frameRect.top - }; - const result = searchInIframes(iframeDoc, newOffset, frame.contentWindow); - if (result) { - return result; - } - } - } catch (_e) { - } - } - } catch (_e) { - } - return null; - }; - - const result = searchInIframes(selfWin.document); - return result || {element: null, frameOffset: {top: 0}}; - }; - - const getFrameOffsetForCurrentWindow = () => { - if (topWin === selfWin) { - return {top: 0}; - } - try { - const frames = topWin.document.getElementsByTagName('iframe'); - for (const frame of frames) { - if (frame.contentWindow === selfWin) { - return {top: getBoundingClientRect(frame).top}; - } - } - } catch (_e) { - return {top: 0}; - } - return {top: 0}; - }; - const getViewportHeight = () => { - return topWin.innerHeight || topWin.document.documentElement.clientHeight || topWin.document.body.clientHeight || 0; + const dim = getWinDimensions(); + return dim.innerHeight || dim.document.documentElement.clientHeight || dim.document.body.clientHeight || 0; }; const getPageHeight = () => { - const body = topWin.document.body; - const html = topWin.document.documentElement; + const dim = getWinDimensions(); + const body = dim.document.body; + const html = dim.document.documentElement; if (!body || !html) return 0; return Math.max( @@ -86,8 +32,8 @@ export function getPlacementPositionUtils() { const elementRect = getBoundingClientRect(element); if (!elementRect) return {distanceToView: 0, elementHeight: 0}; - const elementTop = elementRect.top + frameOffset.top; - const elementBottom = elementRect.bottom + frameOffset.top; + const elementTop = elementRect.top + frameOffset.y; + const elementBottom = elementRect.bottom + frameOffset.y; const viewportHeight = getViewportHeight(); let distanceToView; @@ -103,7 +49,8 @@ export function getPlacementPositionUtils() { }; function getPlacementInfo(bidReq) { - const {element, frameOffset, iframeWindow} = findElementWithContext(bidReq.adUnitCode); + const element = selfWin.document.getElementById(bidReq.adUnitCode); + const frameOffset = getViewportOffset(); const {distanceToView, elementHeight} = getViewableDistance(element, frameOffset); const sizes = (bidReq.sizes || []).map(size => ({ @@ -114,8 +61,7 @@ export function getPlacementPositionUtils() { ? sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min, sizes[0]) : {}; - const winForViewability = iframeWindow || topWin; - const placementPercentView = element ? getViewability(element, winForViewability, size) : 0; + const placementPercentView = element ? getViewability(element, topWin, size) : 0; return cleanObj({ AuctionsCount: bidReq.auctionsCount, diff --git a/test/spec/libraries/placementPositionInfo_spec.js b/test/spec/libraries/placementPositionInfo_spec.js index 11a3aa1aa0f..9d8a57f4db4 100644 --- a/test/spec/libraries/placementPositionInfo_spec.js +++ b/test/spec/libraries/placementPositionInfo_spec.js @@ -2,6 +2,8 @@ import { getPlacementPositionUtils } from '../../../libraries/placementPositionI 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 assert from 'assert'; import sinon from 'sinon'; @@ -16,6 +18,7 @@ describe('placementPositionInfo', function () { let mockDocument; let mockWindow; + let viewportOffset beforeEach(function () { sandbox = sinon.createSandbox(); @@ -39,6 +42,9 @@ describe('placementPositionInfo', function () { getBoundingClientRectStub = sandbox.stub(boundingClientRectLib, 'getBoundingClientRect'); percentInViewStub = sandbox.stub(percentInViewLib, 'getViewability'); cleanObjStub = sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); + sandbox.stub(winDimensions, 'getWinDimensions').returns(mockWindow); + viewportOffset = {x: 0, y: 0}; + sandbox.stub(percentInViewLib, 'getViewportOffset').callsFake(() => viewportOffset); }); afterEach(function () { @@ -393,87 +399,18 @@ describe('placementPositionInfo', function () { }); describe('iframe coordinate translation', function () { - let mockSelfDoc; - let mockSelfWindow; - let mockTopWindow; - let mockIframe; - let iframeTop; - - beforeEach(function () { - iframeTop = 0; - mockSelfDoc = { - getElementById: sandbox.stub().returns({ id: 'test' }), - getElementsByTagName: sandbox.stub().returns([]) - }; - mockSelfWindow = { - innerHeight: 500, - document: mockSelfDoc - }; - mockTopWindow = { - innerHeight: 1000, - document: { - getElementsByTagName: sinon.stub(), - body: { scrollHeight: 2000, offsetHeight: 1800 }, - documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } - } - }; - mockIframe = { - contentWindow: mockSelfWindow - }; - - sandbox.restore(); - sandbox = sinon.createSandbox(); - - mockSelfDoc.getElementById = sandbox.stub().returns({ id: 'test' }); - mockSelfDoc.getElementsByTagName = sandbox.stub().returns([]); - mockTopWindow.document.getElementsByTagName = sandbox.stub(); - - sandbox.stub(utils, 'canAccessWindowTop').returns(true); - sandbox.stub(utils, 'getWindowTop').returns(mockTopWindow); - sandbox.stub(utils, 'getWindowSelf').returns(mockSelfWindow); - sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); - getBoundingClientRectStub = sandbox.stub(boundingClientRectLib, 'getBoundingClientRect'); - percentInViewStub = sandbox.stub(percentInViewLib, 'getViewability').returns(0); - }); - - it('should return frame offset of 0 when not in iframe (topWin === selfWin)', function () { - sandbox.restore(); - sandbox = sinon.createSandbox(); - - const sameDoc = { - getElementById: sandbox.stub().returns({ id: 'test' }), - getElementsByTagName: sandbox.stub().returns([]), - body: { scrollHeight: 2000, offsetHeight: 1800 }, - documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } - }; - const sameWindow = { - innerHeight: 800, - document: sameDoc - }; - sandbox.stub(utils, 'canAccessWindowTop').returns(true); - sandbox.stub(utils, 'getWindowTop').returns(sameWindow); - sandbox.stub(utils, 'getWindowSelf').returns(sameWindow); - sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); - sandbox.stub(boundingClientRectLib, 'getBoundingClientRect').returns({ - top: 100, bottom: 200, height: 100 - }); - sandbox.stub(percentInViewLib, 'getViewability').returns(0); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'test', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 0); + beforeEach(() => { + mockDocument.getElementById = sandbox.stub().returns({id: 'test'}); + mockWindow.innerHeight = 1000; + mockDocument.body = { + scrollHeight: 2000, offsetHeight: 1800 + } + mockDocument.documentElement = { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } }); - it('should apply iframe offset when running inside a friendly iframe', function () { - iframeTop = 200; - mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + viewportOffset = {y: 200}; getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; return { top: 100, bottom: 200, height: 100 }; }); @@ -487,11 +424,9 @@ describe('placementPositionInfo', function () { }); it('should calculate correct distance when element is below viewport with iframe offset', function () { - iframeTop = 500; - mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + viewportOffset = {y: 500}; getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; return { top: 600, bottom: 700, height: 100 }; }); @@ -505,11 +440,9 @@ describe('placementPositionInfo', function () { }); it('should calculate negative distance when element is above viewport with iframe offset', function () { - iframeTop = -600; - mockTopWindow.document.getElementsByTagName.returns([mockIframe]); + viewportOffset = {y: -600}; getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; return { top: 100, bottom: 200, height: 100 }; }); @@ -521,221 +454,5 @@ describe('placementPositionInfo', function () { assert.strictEqual(result.DistanceToView, -400); }); - - it('should return frame offset of 0 when iframe is not found', function () { - mockTopWindow.document.getElementsByTagName.returns([]); - - getBoundingClientRectStub.returns({ - top: 100, bottom: 200, height: 100 - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'test', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 0); - }); - - it('should return frame offset of 0 when getElementsByTagName throws', function () { - mockTopWindow.document.getElementsByTagName.throws(new Error('Access denied')); - - getBoundingClientRectStub.returns({ - top: 100, bottom: 200, height: 100 - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'test', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 0); - }); - - it('should use top window viewport height for distance calculation', function () { - iframeTop = 0; - mockTopWindow.document.getElementsByTagName.returns([mockIframe]); - - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 1200, bottom: 1300, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'test', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 200); - }); - }); - - describe('FIF scenario: Prebid in parent, element in iframe', function () { - let mockElement; - let mockIframeDoc; - let mockIframeWindow; - let mockIframe; - let mockParentDoc; - let mockParentWindow; - let iframeTop; - - beforeEach(function () { - iframeTop = 200; - mockElement = { id: 'iframe-ad-unit' }; - mockIframeWindow = { innerHeight: 400 }; - mockIframeDoc = { - getElementById: sinon.stub().returns(mockElement), - getElementsByTagName: sinon.stub().returns([]) - }; - mockIframe = { - contentDocument: mockIframeDoc, - contentWindow: mockIframeWindow - }; - - sandbox.restore(); - sandbox = sinon.createSandbox(); - - mockIframeDoc.getElementById = sandbox.stub().returns(mockElement); - mockIframeDoc.getElementsByTagName = sandbox.stub().returns([]); - - mockParentDoc = { - getElementById: sandbox.stub().returns(null), - getElementsByTagName: sandbox.stub().returns([mockIframe]), - body: { scrollHeight: 2000, offsetHeight: 1800 }, - documentElement: { clientHeight: 1900, scrollHeight: 2100, offsetHeight: 1950 } - }; - - mockParentWindow = { - innerHeight: 800, - document: mockParentDoc - }; - - sandbox.stub(utils, 'canAccessWindowTop').returns(true); - sandbox.stub(utils, 'getWindowTop').returns(mockParentWindow); - sandbox.stub(utils, 'getWindowSelf').returns(mockParentWindow); - sandbox.stub(utils, 'cleanObj').callsFake(obj => obj); - - getBoundingClientRectStub = sandbox.stub(boundingClientRectLib, 'getBoundingClientRect'); - percentInViewStub = sandbox.stub(percentInViewLib, 'getViewability').returns(50); - }); - - it('should find element in iframe document when not in current document', function () { - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 100, bottom: 200, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'iframe-ad-unit', - sizes: [[300, 250]] - }); - - assert.ok(mockIframeDoc.getElementById.calledWith('iframe-ad-unit')); - assert.strictEqual(result.ElementHeight, 100); - }); - - it('should apply iframe offset when element is in iframe', function () { - iframeTop = 300; - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 100, bottom: 200, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'iframe-ad-unit', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 0); - }); - - it('should calculate positive distance when element in iframe is below viewport', function () { - iframeTop = 500; - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 400, bottom: 500, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'iframe-ad-unit', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 100); - }); - - it('should calculate negative distance when element in iframe is above viewport', function () { - iframeTop = -500; - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 100, bottom: 200, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'iframe-ad-unit', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, -300); - }); - - it('should use iframe window for viewability calculation', function () { - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 100, bottom: 200, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - placementUtils.getPlacementInfo({ - adUnitCode: 'iframe-ad-unit', - sizes: [[300, 250]] - }); - - const viewabilityCall = percentInViewStub.getCall(0); - assert.strictEqual(viewabilityCall.args[1], mockIframeWindow); - }); - - it('should skip cross-origin iframes that throw errors', function () { - const crossOriginIframe = { - get contentDocument() { throw new Error('Blocked by CORS'); }, - get contentWindow() { return null; } - }; - mockParentDoc.getElementsByTagName.returns([crossOriginIframe, mockIframe]); - - getBoundingClientRectStub.callsFake((el) => { - if (el === mockIframe) return { top: iframeTop }; - return { top: 100, bottom: 200, height: 100 }; - }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'iframe-ad-unit', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.ElementHeight, 100); - }); - - it('should return default values when element not found anywhere', function () { - mockIframeDoc.getElementById.returns(null); - - getBoundingClientRectStub.returns({ top: 100, bottom: 200, height: 100 }); - - const placementUtils = getPlacementPositionUtils(); - const result = placementUtils.getPlacementInfo({ - adUnitCode: 'non-existent', - sizes: [[300, 250]] - }); - - assert.strictEqual(result.DistanceToView, 0); - assert.strictEqual(result.ElementHeight, 1); - }); }); }); From d31c3447c62f65de9fb5492a8049ec180c7588b3 Mon Sep 17 00:00:00 2001 From: PanxoDev Date: Thu, 12 Feb 2026 11:59:36 +0100 Subject: [PATCH 044/105] Panxo RTD Provider: initial release (#14419) * New module: Panxo RTD Provider Add Panxo RTD submodule that enriches OpenRTB bid requests with real-time AI traffic classification signals through device.ext.panxo and site.ext.data.panxo. * fix: fail open when bridge is unavailable and guard null messages - Flush pending auction callbacks when the implementation bridge cannot be reached, so auctions are never blocked indefinitely. - Guard against null bridge messages to prevent runtime errors. - Add tests for both scenarios. --------- Co-authored-by: Monis Qadri --- modules/.submodules.json | 1 + modules/panxoRtdProvider.js | 227 +++++++++++++ modules/panxoRtdProvider.md | 45 +++ .../eslint/approvedLoadExternalScriptPaths.js | 1 + test/spec/modules/panxoRtdProvider_spec.js | 311 ++++++++++++++++++ 5 files changed, 585 insertions(+) create mode 100644 modules/panxoRtdProvider.js create mode 100644 modules/panxoRtdProvider.md create mode 100644 test/spec/modules/panxoRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index d87b73401cd..c6b0db32e78 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -118,6 +118,7 @@ "optimeraRtdProvider", "overtoneRtdProvider", "oxxionRtdProvider", + "panxoRtdProvider", "permutiveRtdProvider", "pubmaticRtdProvider", "pubxaiRtdProvider", diff --git a/modules/panxoRtdProvider.js b/modules/panxoRtdProvider.js new file mode 100644 index 00000000000..5865106e564 --- /dev/null +++ b/modules/panxoRtdProvider.js @@ -0,0 +1,227 @@ +/** + * This module adds Panxo AI traffic classification to the real time data module. + * + * The {@link module:modules/realTimeData} module is required. + * The module injects the Panxo signal collection script, enriching bid requests + * with AI traffic classification data and contextual signals for improved targeting. + * @module modules/panxoRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { + prefixLog, + mergeDeep, + generateUUID, + getWindowSelf, +} from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + +const SUBMODULE_NAME = 'panxo'; +const SCRIPT_URL = 'https://api.idsequoia.ai/rtd.js'; + +const { logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); + +/** @type {string} */ +let siteId = ''; + +/** @type {boolean} */ +let verbose = false; + +/** @type {string} */ +let sessionId = ''; + +/** @type {Object} */ +let panxoData = {}; + +/** @type {boolean} */ +let implReady = false; + +/** @type {Array} */ +let pendingCallbacks = []; + +/** + * Submodule registration + */ +function main() { + submodule('realTimeData', /** @type {RtdSubmodule} */ ({ + name: SUBMODULE_NAME, + + init: (config, userConsent) => { + try { + load(config); + return true; + } catch (err) { + logError('init', err.message); + return false; + } + }, + + getBidRequestData: onGetBidRequestData + })); +} + +/** + * Validates configuration and loads the Panxo signal collection script. + * @param {SubmoduleConfig} config + */ +function load(config) { + siteId = config?.params?.siteId || ''; + if (!siteId || typeof siteId !== 'string') { + throw new Error(`The 'siteId' parameter is required and must be a string`); + } + + // siteId is a 16-character hex hash identifying the publisher property + if (!/^[a-f0-9]{16}$/.test(siteId)) { + throw new Error(`The 'siteId' parameter must be a valid 16-character hex identifier`); + } + + // Load/reset the state + verbose = !!config?.params?.verbose; + sessionId = generateUUID(); + panxoData = {}; + implReady = false; + pendingCallbacks = []; + + const refDomain = getRefererInfo().domain || ''; + + // The implementation script uses the session parameter to register + // a bridge API on window['panxo_' + sessionId] + const scriptUrl = `${SCRIPT_URL}?siteId=${siteId}&session=${sessionId}&r=${refDomain}`; + + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, onImplLoaded); +} + +/** + * Callback invoked when the external script finishes loading. + * Establishes the bridge between this RTD submodule and the implementation. + */ +function onImplLoaded() { + const wnd = getWindowSelf(); + const impl = wnd[`panxo_${sessionId}`]; + if (typeof impl !== 'object' || typeof impl.connect !== 'function') { + if (verbose) logWarn('onload', 'Unable to access the implementation script'); + if (!implReady) { + implReady = true; + flushPendingCallbacks(); + } + return; + } + + // Set up the bridge. The callback may be called multiple times as + // more precise signal data becomes available. + impl.connect(getGlobal(), onImplMessage); +} + +/** + * Bridge callback invoked by the implementation script to update signal data. + * When the first signal arrives, flushes any pending auction callbacks so + * the auction can proceed with enriched data. + * @param {Object} msg + */ +function onImplMessage(msg) { + if (!msg || typeof msg !== 'object') { + return; + } + + switch (msg.type) { + case 'signal': { + panxoData = mergeDeep({}, msg.data || {}); + if (!implReady) { + implReady = true; + flushPendingCallbacks(); + } + break; + } + case 'error': { + logError('impl', msg.data || ''); + if (!implReady) { + implReady = true; + flushPendingCallbacks(); + } + break; + } + } +} + +/** + * Flush all pending getBidRequestData callbacks. + * Called when the implementation script sends its first signal. + */ +function flushPendingCallbacks() { + const cbs = pendingCallbacks.splice(0); + cbs.forEach(cb => cb()); +} + +/** + * Called once per auction to enrich bid request ORTB data. + * + * If the implementation script has already sent signal data, enrichment + * happens synchronously and the callback fires immediately. Otherwise the + * callback is deferred until the first signal arrives. The Prebid RTD + * framework enforces `auctionDelay` as the upper bound on this wait, so + * the auction is never blocked indefinitely. + * + * Adds the following fields: + * - device.ext.panxo: session signal token for traffic verification + * - site.ext.data.panxo: contextual AI traffic classification data + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ +function onGetBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + function enrichAndDone() { + const ortb2 = {}; + + // Add device-level signal (opaque session token) + if (panxoData.device) { + mergeDeep(ortb2, { device: { ext: { panxo: panxoData.device } } }); + } + + // Add site-level contextual data (AI classification) + if (panxoData.site && Object.keys(panxoData.site).length > 0) { + mergeDeep(ortb2, { site: { ext: { data: { panxo: panxoData.site } } } }); + } + + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, ortb2); + callback(); + } + + // If data already arrived, proceed immediately + if (implReady) { + enrichAndDone(); + return; + } + + // Otherwise, wait for the implementation script to send its first signal. + // The auctionDelay configured by the publisher (e.g. 1500ms) acts as the + // maximum wait time -- Prebid will call our callback when it expires. + pendingCallbacks.push(enrichAndDone); +} + +/** + * Exporting local functions for testing purposes. + */ +export const __TEST__ = { + SUBMODULE_NAME, + SCRIPT_URL, + main, + load, + onImplLoaded, + onImplMessage, + onGetBidRequestData, + flushPendingCallbacks +}; + +main(); diff --git a/modules/panxoRtdProvider.md b/modules/panxoRtdProvider.md new file mode 100644 index 00000000000..bc9be90d152 --- /dev/null +++ b/modules/panxoRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Panxo RTD Provider +Module Type: RTD Provider +Maintainer: prebid@panxo.ai +``` + +# Description + +The Panxo RTD module enriches OpenRTB bid requests with real-time AI traffic classification signals. It detects visits originating from AI assistants and provides contextual data through `device.ext.panxo` and `site.ext.data.panxo`, enabling the Panxo Bid Adapter and other demand partners to apply differentiated bidding on AI-referred inventory. + +To use this module, contact [publishers@panxo.ai](mailto:publishers@panxo.ai) or sign up at [app.panxo.com](https://app.panxo.com) to receive your property identifier. + +# Build + +```bash +gulp build --modules=rtdModule,panxoRtdProvider,... +``` + +> `rtdModule` is required to use the Panxo RTD module. + +# Configuration + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 300, + dataProviders: [{ + name: 'panxo', + waitForIt: true, + params: { + siteId: 'a1b2c3d4e5f67890' + } + }] + } +}); +``` + +## Parameters + +| Name | Type | Description | Required | +| :-------- | :------ | :----------------------------------------------------- | :------- | +| `siteId` | String | 16-character hex property identifier provided by Panxo | Yes | +| `verbose` | Boolean | Enable verbose logging for troubleshooting | No | diff --git a/plugins/eslint/approvedLoadExternalScriptPaths.js b/plugins/eslint/approvedLoadExternalScriptPaths.js index 95552535439..0eab8545924 100644 --- a/plugins/eslint/approvedLoadExternalScriptPaths.js +++ b/plugins/eslint/approvedLoadExternalScriptPaths.js @@ -32,6 +32,7 @@ const APPROVED_LOAD_EXTERNAL_SCRIPT_PATHS = [ 'modules/anonymisedRtdProvider.js', 'modules/optableRtdProvider.js', 'modules/oftmediaRtdProvider.js', + 'modules/panxoRtdProvider.js', // UserId Submodules 'modules/justIdSystem.js', 'modules/tncIdSystem.js', diff --git a/test/spec/modules/panxoRtdProvider_spec.js b/test/spec/modules/panxoRtdProvider_spec.js new file mode 100644 index 00000000000..d3e0cbe12bf --- /dev/null +++ b/test/spec/modules/panxoRtdProvider_spec.js @@ -0,0 +1,311 @@ +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js'; +import * as refererDetection from '../../../src/refererDetection.js'; + +import { __TEST__ } from '../../../modules/panxoRtdProvider.js'; + +const { + SUBMODULE_NAME, + SCRIPT_URL, + main, + load, + onImplLoaded, + onImplMessage, + onGetBidRequestData, + flushPendingCallbacks +} = __TEST__; + +describe('panxo RTD module', function () { + let sandbox; + + const stubUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + const panxoBridgeId = `panxo_${stubUuid}`; + const stubWindow = { [panxoBridgeId]: undefined }; + + const validSiteId = 'a1b2c3d4e5f67890'; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'getWindowSelf').returns(stubWindow); + sandbox.stub(utils, 'generateUUID').returns(stubUuid); + sandbox.stub(refererDetection, 'getRefererInfo').returns({ domain: 'example.com' }); + }); + afterEach(function() { + sandbox.restore(); + }); + + describe('Initialization step', function () { + let sandbox2; + let connectSpy; + beforeEach(function() { + sandbox2 = sinon.createSandbox(); + connectSpy = sandbox.spy(); + // Simulate: once the impl script is loaded, it registers the bridge API + sandbox2.stub(stubWindow, panxoBridgeId).value({ connect: connectSpy }); + }); + afterEach(function () { + sandbox2.restore(); + }); + + it('should accept valid configuration with siteId', function () { + expect(() => load({ params: { siteId: validSiteId } })).to.not.throw(); + }); + + it('should throw an Error when siteId is missing', function () { + expect(() => load({})).to.throw(); + expect(() => load({ params: {} })).to.throw(); + expect(() => load({ params: { siteId: '' } })).to.throw(); + }); + + it('should throw an Error when siteId is not a valid hex string', function () { + expect(() => load({ params: { siteId: 'abc' } })).to.throw(); + expect(() => load({ params: { siteId: 123 } })).to.throw(); + expect(() => load({ params: { siteId: 'zzzzzzzzzzzzzzzz' } })).to.throw(); + expect(() => load({ params: { siteId: 'a1b2c3d4e5f6789' } })).to.throw(); // 15 chars + expect(() => load({ params: { siteId: 'a1b2c3d4e5f678901' } })).to.throw(); // 17 chars + }); + + it('should insert implementation script with correct URL', () => { + load({ params: { siteId: validSiteId } }); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + + const args = loadExternalScriptStub.getCall(0).args; + expect(args[0]).to.be.equal( + `${SCRIPT_URL}?siteId=${validSiteId}&session=${stubUuid}&r=example.com` + ); + expect(args[2]).to.be.equal(SUBMODULE_NAME); + expect(args[3]).to.be.equal(onImplLoaded); + }); + + it('should connect to the implementation script once it loads', function () { + load({ params: { siteId: validSiteId } }); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + expect(connectSpy.calledOnce).to.be.true; + + const args = connectSpy.getCall(0).args; + expect(args[0]).to.haveOwnProperty('cmd'); // pbjs global + expect(args[0]).to.haveOwnProperty('que'); + expect(args[1]).to.be.equal(onImplMessage); + }); + + it('should flush pending callbacks when bridge is unavailable', function () { + sandbox2.restore(); + // Bridge is not registered on the window -- onImplLoaded should fail open + sandbox2 = sinon.createSandbox(); + sandbox2.stub(stubWindow, panxoBridgeId).value(undefined); + + load({ params: { siteId: validSiteId } }); + + const callbackSpy = sandbox2.spy(); + const reqBidsConfig = { ortb2Fragments: { bidder: {}, global: {} } }; + + // Queue a callback before bridge fails + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + // onImplLoaded already ran (bridge undefined) and flushed + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('should not throw when bridge message is null', function () { + load({ params: { siteId: validSiteId } }); + expect(() => onImplMessage(null)).to.not.throw(); + expect(() => onImplMessage(undefined)).to.not.throw(); + }); + }); + + describe('Bid enrichment step', function () { + const signalData = { + device: { v1: 'session-token-123' }, + site: { ai: true, src: 'chatgpt', conf: 0.95, seg: 'technology', co: 'US' } + }; + + let sandbox2; + let callbackSpy; + let reqBidsConfig; + beforeEach(function() { + sandbox2 = sinon.createSandbox(); + callbackSpy = sandbox2.spy(); + reqBidsConfig = { ortb2Fragments: { bidder: {}, global: {} } }; + // Prevent onImplLoaded from firing automatically so tests can + // control module readiness via onImplMessage directly. + loadExternalScriptStub.callsFake(() => {}); + }); + afterEach(function () { + loadExternalScriptStub.reset(); + sandbox2.restore(); + }); + + it('should defer callback when implementation has not sent a signal yet', () => { + load({ params: { siteId: validSiteId } }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + // Callback is deferred until the implementation script sends its first signal + expect(callbackSpy.notCalled).to.be.true; + }); + + it('should flush deferred callback once a signal arrives', () => { + load({ params: { siteId: validSiteId } }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + expect(callbackSpy.notCalled).to.be.true; + + // First signal arrives -- deferred callback should now fire + onImplMessage({ type: 'signal', data: { device: { v1: 'tok' }, site: {} } }); + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('should flush all deferred callbacks when the first signal arrives', () => { + load({ params: { siteId: validSiteId } }); + + const spy1 = sandbox2.spy(); + const spy2 = sandbox2.spy(); + const cfg1 = { ortb2Fragments: { bidder: {}, global: {} } }; + const cfg2 = { ortb2Fragments: { bidder: {}, global: {} } }; + + onGetBidRequestData(cfg1, spy1, { params: {} }, {}); + onGetBidRequestData(cfg2, spy2, { params: {} }, {}); + expect(spy1.notCalled).to.be.true; + expect(spy2.notCalled).to.be.true; + + onImplMessage({ type: 'signal', data: { device: { v1: 'tok' }, site: {} } }); + expect(spy1.calledOnce).to.be.true; + expect(spy2.calledOnce).to.be.true; + }); + + it('should call callback immediately once implementation is ready', () => { + load({ params: { siteId: validSiteId } }); + + // Mark implementation as ready via a signal + onImplMessage({ type: 'signal', data: { device: { v1: 'tok' }, site: {} } }); + + // Subsequent calls should resolve immediately + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('should call callback when implementation reports an error', () => { + load({ params: { siteId: validSiteId } }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + expect(callbackSpy.notCalled).to.be.true; + + // An error still unblocks the auction + onImplMessage({ type: 'error', data: 'some error' }); + expect(callbackSpy.calledOnce).to.be.true; + // No device or site should be added since panxoData is empty + }); + + it('should add device.ext.panxo with session token when signal is received', () => { + load({ params: { siteId: validSiteId } }); + + onImplMessage({ type: 'signal', data: signalData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); + expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('panxo') + .which.is.an('object') + .that.deep.equals(signalData.device); + }); + + it('should add site.ext.data.panxo with AI classification data', () => { + load({ params: { siteId: validSiteId } }); + + onImplMessage({ type: 'signal', data: signalData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('site'); + expect(reqBidsConfig.ortb2Fragments.global.site).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.site.ext).to.have.own.property('data'); + expect(reqBidsConfig.ortb2Fragments.global.site.ext.data).to.have.own.property('panxo') + .which.is.an('object') + .that.deep.equals(signalData.site); + }); + + it('should update panxo data when new signal is received', () => { + load({ params: { siteId: validSiteId } }); + + const updatedData = { + device: { v1: 'updated-token' }, + site: { ai: true, src: 'perplexity', conf: 0.88, seg: 'finance', co: 'UK' } + }; + + onImplMessage({ type: 'signal', data: { device: { v1: 'old-token' }, site: {} } }); + onImplMessage({ type: 'signal', data: updatedData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global.device.ext.panxo) + .to.deep.equal(updatedData.device); + expect(reqBidsConfig.ortb2Fragments.global.site.ext.data.panxo) + .to.deep.equal(updatedData.site); + }); + + it('should not add site data when site object is empty', () => { + load({ params: { siteId: validSiteId } }); + + onImplMessage({ type: 'signal', data: { device: { v1: 'token' }, site: {} } }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global.device.ext.panxo) + .to.deep.equal({ v1: 'token' }); + // site should not have panxo data since it was empty + expect(reqBidsConfig.ortb2Fragments.global).to.not.have.own.property('site'); + }); + }); + + describe('Submodule execution', function() { + let sandbox2; + let submoduleStub; + beforeEach(function() { + sandbox2 = sinon.createSandbox(); + submoduleStub = sandbox2.stub(hook, 'submodule'); + }); + afterEach(function () { + sandbox2.restore(); + }); + + function getModule() { + main(); + + expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true); + + const submoduleDef = submoduleStub.getCall(0).args[1]; + expect(submoduleDef).to.be.an('object'); + expect(submoduleDef).to.have.own.property('name', SUBMODULE_NAME); + expect(submoduleDef).to.have.own.property('init').that.is.a('function'); + expect(submoduleDef).to.have.own.property('getBidRequestData').that.is.a('function'); + + return submoduleDef; + } + + it('should register panxo RTD submodule provider', function () { + getModule(); + }); + + it('should refuse initialization when siteId is missing', function () { + const { init } = getModule(); + expect(init({ params: {} })).to.equal(false); + expect(loadExternalScriptStub.notCalled).to.be.true; + }); + + it('should refuse initialization when siteId is invalid', function () { + const { init } = getModule(); + expect(init({ params: { siteId: 'invalid' } })).to.equal(false); + expect(loadExternalScriptStub.notCalled).to.be.true; + }); + + it('should commence initialization with valid siteId', function () { + const { init } = getModule(); + expect(init({ params: { siteId: validSiteId } })).to.equal(true); + expect(loadExternalScriptStub.calledOnce).to.be.true; + }); + }); +}); From 79d4aa6857e9273e8ccba4fad105e094fbcdd518 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 12 Feb 2026 04:28:47 -0800 Subject: [PATCH 045/105] Core: remove type declaration for `getStatusCode` (#14431) * Core: reintroduce bidResponse.getStatusCode * Revert "Core: reintroduce bidResponse.getStatusCode" This reverts commit dcf61ba5614144355f0f0b3a9c3764e618802265. * Core: remove getStatusCode typing --- src/bidfactory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bidfactory.ts b/src/bidfactory.ts index b87d3b14a26..8ce6bda3f23 100644 --- a/src/bidfactory.ts +++ b/src/bidfactory.ts @@ -135,7 +135,6 @@ export interface BaseBid extends ContextIdentifiers, Required Date: Thu, 12 Feb 2026 16:04:25 +0000 Subject: [PATCH 046/105] Prebid 10.25.0 release --- .../gpt/x-domain/creative.html | 2 +- metadata/modules.json | 34 +++++++++++++++++++ metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.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/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 | 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 | 6 ++-- metadata/modules/appStockSSPBidAdapter.json | 2 +- metadata/modules/appierBidAdapter.json | 2 +- metadata/modules/appnexusBidAdapter.json | 8 ++--- 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 | 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 | 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 +- .../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/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/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 | 4 +-- metadata/modules/liveIntentIdSystem.json | 2 +- metadata/modules/liveIntentRtdProvider.json | 2 +- metadata/modules/livewrappedBidAdapter.json | 2 +- metadata/modules/locIdSystem.json | 20 +++++++++++ metadata/modules/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.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/mileBidAdapter.json | 13 +++++++ metadata/modules/minutemediaBidAdapter.json | 2 +- metadata/modules/missenaBidAdapter.json | 2 +- metadata/modules/mobianRtdProvider.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/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/panxoRtdProvider.json | 12 +++++++ 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 | 9 +++-- 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/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 | 4 +-- .../modules/relevantdigitalBidAdapter.json | 2 +- metadata/modules/resetdigitalBidAdapter.json | 2 +- metadata/modules/responsiveAdsBidAdapter.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/setupadBidAdapter.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 | 18 ++++++++++ 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 +- 276 files changed, 396 insertions(+), 294 deletions(-) create mode 100644 metadata/modules/locIdSystem.json create mode 100644 metadata/modules/mileBidAdapter.json create mode 100644 metadata/modules/panxoRtdProvider.json create mode 100644 metadata/modules/yaleoBidAdapter.json diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 14deeb4f559..ae8456c19e0 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' - + `); + adSlotIframe.contentDocument.close(); + + setTimeout(() => { + expect(trackPostStub.calledOnce).to.be.true; + expect(trackPostStub.getCalls()[0].args[1].adUnitId).to.eql('/reconciliationAdunit'); + expect(trackPostStub.getCalls()[0].args[1].adDeliveryId).to.match(/.+-.+/); + done(); + }, 100); + }); }); }); }); diff --git a/test/spec/unit/core/targetingLock_spec.js b/test/spec/unit/core/targetingLock_spec.js index b8e721259ca..89ab7972845 100644 --- a/test/spec/unit/core/targetingLock_spec.js +++ b/test/spec/unit/core/targetingLock_spec.js @@ -98,7 +98,9 @@ describe('Targeting lock', () => { lock.lock(targeting); eventHandlers.slotRenderEnded({ slot: { - getTargeting: (key) => [targeting[key]] + getConfig: sinon.stub().withArgs('targeting').returns({ + k1: [targeting.k1] + }) } }); expect(lock.isLocked(targeting)).to.be.false; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 5939298765e..6e9955081b4 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -82,6 +82,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 +136,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 { diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 084341358b4..509fb25140e 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -552,6 +552,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] || []) } From ebb775ce7a6e601acd670a6f50630c2578609092 Mon Sep 17 00:00:00 2001 From: danijel-ristic <168181386+danijel-ristic@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:43:27 +0100 Subject: [PATCH 054/105] TargetVideo bid adapter: send price floor param (#14406) * TargetVideo bid adapter: send price floor param * Add support for the price floors module * Add imports * Fix getBidFloor function floor params --------- Co-authored-by: dnrstc --- modules/targetVideoBidAdapter.js | 28 +++++++++- modules/targetVideoBidAdapter.md | 1 + .../modules/targetVideoBidAdapter_spec.js | 54 ++++++++++++++++--- 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index 84730231543..723c9d77dbd 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -1,4 +1,4 @@ -import {_each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; +import {_each, deepAccess, getDefinedParams, isFn, isPlainObject, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {formatRequest, getRtbBid, getSiteObj, getSyncResponse, videoBid, bannerBid, createVideoTag} from '../libraries/targetVideoUtils/bidderUtils.js'; @@ -9,6 +9,22 @@ import {SOURCE, GVLID, BIDDER_CODE, VIDEO_PARAMS, BANNER_ENDPOINT_URL, VIDEO_END * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.floor) ? bid.params.floor : null; + } + + const floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { + return floor.floor; + } + return null; +} + export const spec = { code: BIDDER_CODE, @@ -38,13 +54,15 @@ export const spec = { version: '$prebid.version$' }; - for (let {params, bidId, sizes, mediaTypes, ...bid} of bidRequests) { + for (let {bidId, sizes, mediaTypes, ...bid} of bidRequests) { for (const mediaType in mediaTypes) { switch (mediaType) { case VIDEO: { + const params = bid.params; const video = mediaTypes[VIDEO]; const placementId = params.placementId; const site = getSiteObj(); + const floor = getBidFloor(bid); if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; @@ -71,6 +89,12 @@ export const spec = { video: getDefinedParams(video, VIDEO_PARAMS) } + const bidFloor = typeof floor === 'string' ? Number(floor.trim()) + : typeof floor === 'number' ? floor + : NaN; + + if (Number.isFinite(bidFloor) && bidFloor > 0) imp.bidfloor = bidFloor; + if (video.playerSize) { imp.video = Object.assign( imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {} diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md index a34ad0aff27..9a204a86991 100644 --- a/modules/targetVideoBidAdapter.md +++ b/modules/targetVideoBidAdapter.md @@ -43,6 +43,7 @@ var adUnits = [ bidder: 'targetVideo', params: { placementId: 12345, + floor: 2, reserve: 0, } }] diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index 838fd50f76d..9b91e71c7d8 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -7,6 +7,13 @@ describe('TargetVideo Bid Adapter', function() { const params = { placementId: 12345, }; + const videoMediaTypes = { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } + } const defaultBidderRequest = { bidderRequestId: 'mock-uuid', @@ -25,13 +32,7 @@ describe('TargetVideo Bid Adapter', function() { const videoRequest = [{ bidder, params, - mediaTypes: { - video: { - playerSize: [[640, 360]], - context: 'instream', - playbackmethod: [1, 2, 3, 4] - } - } + mediaTypes: videoMediaTypes, }]; it('Test the bid validation function', function() { @@ -353,4 +354,43 @@ describe('TargetVideo Bid Adapter', function() { const userSyncs = spec.getUserSyncs({iframeEnabled: false}); expect(userSyncs).to.have.lengthOf(0); }); + + it('Test the VIDEO request floor param', function() { + const requests = [ + { + bidder, + params: { + ...params, + floor: 2.12, + }, + mediaTypes: videoMediaTypes, + }, + { + bidder, + params: { + ...params, + floor: "1.55", + }, + mediaTypes: videoMediaTypes, + }, + { + bidder, + params: { + ...params, + floor: "abc", + }, + mediaTypes: videoMediaTypes, + } + ] + + const bids = spec.buildRequests(requests, defaultBidderRequest) + + const payload1 = JSON.parse(bids[0].data); + const payload2 = JSON.parse(bids[1].data); + const payload3 = JSON.parse(bids[2].data); + + expect(payload1.imp[0].bidfloor).to.exist.and.equal(2.12); + expect(payload2.imp[0].bidfloor).to.exist.and.equal(1.55); + expect(payload3.imp[0].bidfloor).to.not.exist; + }); }); From 50bc0369c6ed6a071139fb19a821ca0bf0853c53 Mon Sep 17 00:00:00 2001 From: verben-gh Date: Fri, 20 Feb 2026 21:10:28 +0200 Subject: [PATCH 055/105] New adapter: Verben (#14494) Co-authored-by: Verben Co-authored-by: Patrick McCann --- modules/verbenBidAdapter.js | 17 + modules/verbenBidAdapter.md | 79 ++++ test/spec/modules/verbenBidAdapter_spec.js | 478 +++++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 modules/verbenBidAdapter.js create mode 100644 modules/verbenBidAdapter.md create mode 100644 test/spec/modules/verbenBidAdapter_spec.js diff --git a/modules/verbenBidAdapter.js b/modules/verbenBidAdapter.js new file mode 100644 index 00000000000..867f669e93e --- /dev/null +++ b/modules/verbenBidAdapter.js @@ -0,0 +1,17 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'verben'; +const AD_URL = 'https://east-node.verben.com/pbjs'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse +}; + +registerBidder(spec); diff --git a/modules/verbenBidAdapter.md b/modules/verbenBidAdapter.md new file mode 100644 index 00000000000..2d0978bc52f --- /dev/null +++ b/modules/verbenBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Verben Bidder Adapter +Module Type: Verben Bidder Adapter +Maintainer: support_trading@verben.com +``` + +# Description + +Connects to Verben exchange for bids. +Verben bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'verben', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'verben', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'verben', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/verbenBidAdapter_spec.js b/test/spec/modules/verbenBidAdapter_spec.js new file mode 100644 index 00000000000..9864e9d2b70 --- /dev/null +++ b/test/spec/modules/verbenBidAdapter_spec.js @@ -0,0 +1,478 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/verbenBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'verben'; + +describe('VerbenBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://east-node.verben.com/pbjs'); + }); + + it('Returns general data valid', function () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + const serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + const serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 9459b523c547014940f9f149f75c79e148e60683 Mon Sep 17 00:00:00 2001 From: johnclc Date: Fri, 20 Feb 2026 19:11:40 +0000 Subject: [PATCH 056/105] Teal bid adapter: include native and video media types (#14493) --- modules/tealBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/tealBidAdapter.js b/modules/tealBidAdapter.js index 4374646b102..f9175ffb564 100644 --- a/modules/tealBidAdapter.js +++ b/modules/tealBidAdapter.js @@ -1,7 +1,7 @@ import {deepSetValue, deepAccess, triggerPixel, deepClone, isEmpty, logError, shuffle} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js' const BIDDER_CODE = 'teal'; const GVLID = 1378; @@ -43,7 +43,7 @@ const converter = ortbConverter({ export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], aliases: [], isBidRequestValid: function(bid) { From 956dea0d8e1065ec8f37d88dd010b35161ad68e3 Mon Sep 17 00:00:00 2001 From: Anthony Date: Fri, 20 Feb 2026 20:13:00 +0100 Subject: [PATCH 057/105] Proxistore Bid Adapter: migration to OpenRTB (#14411) * Update Proxistore endpoint URLs in adapter and tests Updated the Proxistore `COOKIE_BASE_URL` and `COOKIE_LESS_URL` to the new `abs` domain in both the adapter and its corresponding test file. This ensures consistency with the updated API endpoints. * Integrate OpenRTB converter for Proxistore bid adapter, and add OpenRTB request as a separate field. * Refactor Proxistore bid adapter to improve OpenRTB handling, add GDPR-specific URL selection, and enhance test coverage. * Add support for website and language parameters in Proxistore bid adapter requests, with corresponding test coverage. * Handle empty response body in Proxistore bid adapter to avoid runtime errors. --------- Co-authored-by: Anthony Richir --- .../gpt/proxistore_example.html | 2 +- modules/proxistoreBidAdapter.js | 241 ++++--- .../spec/modules/proxistoreBidAdapter_spec.js | 588 ++++++++++++++---- 3 files changed, 566 insertions(+), 265 deletions(-) diff --git a/integrationExamples/gpt/proxistore_example.html b/integrationExamples/gpt/proxistore_example.html index 15b33b345d1..8c72f23fdbb 100644 --- a/integrationExamples/gpt/proxistore_example.html +++ b/integrationExamples/gpt/proxistore_example.html @@ -1,7 +1,7 @@ - + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/modules/insuradsBidAdapter.md b/modules/insuradsBidAdapter.md new file mode 100644 index 00000000000..11c0bc248e7 --- /dev/null +++ b/modules/insuradsBidAdapter.md @@ -0,0 +1,55 @@ +# Overview + +``` +Module Name: InsurAds Bid Adapter +Module Type: Bidder Adapter +Maintainer: jclimaco@insurads.com +``` + +# Description + +Connects to InsurAds network for bids. + +# Test Parameters + +## Web + +### Display +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'insurads', + params: { + tagId: 'ToBeSupplied' + } + }] + }, +]; +``` + +### Video Instream +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'insurads', + params: { + tagId: 'ToBeSupplied' + } + }] + }; +``` diff --git a/modules/insuradsBidAdapter.ts b/modules/insuradsBidAdapter.ts new file mode 100644 index 00000000000..30f506118b7 --- /dev/null +++ b/modules/insuradsBidAdapter.ts @@ -0,0 +1,149 @@ +import { deepSetValue, generateUUID, logError } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.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, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'insurads'; +const REQUEST_URL = 'https://fast.nexx360.io/booster'; +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '7.1'; +const GVLID = 596; +const ALT_KEY = 'nexx360_storage'; + +const DEFAULT_GZIP_ENABLED = false; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type InsurAdsBidParams = RequireAtLeastOne<{ + tagId?: string; + placement?: string; + videoTagId?: string; + nativeTagId?: string; + adUnitPath?: string; + adUnitName?: string; + divId?: string; + allBids?: boolean; + customId?: string; + bidders?: Record; +}, "tagId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + ['nexx360']: InsurAdsBidParams; + } +} + +export const STORAGE = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +export const getInsurAdsLocalStorage = getLocalStorageFunctionGenerator<{ nexx360Id: string }>( + STORAGE, + BIDDER_CODE, + ALT_KEY, + 'nexx360Id' +); + +export const getGzipSetting = (): boolean => { + const bidderConfig = config.getBidderConfig(); + const gzipEnabled = bidderConfig.insurads?.gzipEnabled; + + if (gzipEnabled === true || gzipEnabled === 'true') { + return true; + } + return DEFAULT_GZIP_ENABLED; +} + +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 divId = bidRequest.params.divId || bidRequest.adUnitCode; + const slotEl: HTMLElement | null = typeof divId === 'string' ? document.getElementById(divId) : null; + 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.nexx360', bidRequest.params); + deepSetValue(imp, 'ext.nexx360.divId', divId); + if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); + if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + let request: ORTBRequest = buildRequest(imps, bidderRequest, context); + const amxId = getAmxId(STORAGE, BIDDER_CODE); + request = enrichRequest(request, amxId, PAGE_VIEW_ID, BIDDER_VERSION); + return request; + }, +}); + +const isBidRequestValid = (bid: BidRequest): boolean => { + if (bid.params.adUnitName && (typeof bid.params.adUnitName !== 'string' || bid.params.adUnitName === '')) { + logError('bid.params.adUnitName needs to be a string'); + return false; + } + if (bid.params.adUnitPath && (typeof bid.params.adUnitPath !== 'string' || bid.params.adUnitPath === '')) { + logError('bid.params.adUnitPath needs to be a string'); + return false; + } + if (bid.params.divId && (typeof bid.params.divId !== 'string' || bid.params.divId === '')) { + logError('bid.params.divId needs to be a string'); + return false; + } + if (bid.params.allBids && typeof bid.params.allBids !== 'boolean') { + logError('bid.params.allBids needs to be a boolean'); + return false; + } + if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId && !bid.params.placement) { + logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId or bid.params.placement must be defined'); + 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: getGzipSetting() + }, + } + 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/insuradsBidAdapter_spec.js b/test/spec/modules/insuradsBidAdapter_spec.js new file mode 100644 index 00000000000..0207477b8e4 --- /dev/null +++ b/test/spec/modules/insuradsBidAdapter_spec.js @@ -0,0 +1,737 @@ +import { expect } from 'chai'; +import { + spec, STORAGE, getInsurAdsLocalStorage, getGzipSetting, +} from 'modules/insuradsBidAdapter.js'; +import sinon from 'sinon'; +import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; +const sandbox = sinon.createSandbox(); + +describe('InsurAds bid adapter tests', () => { + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {}, + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page', + }, + uspConsent: '1112223334', + userId: { id5id: { uid: '1111' } }, + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com', + }], + }, + }; + + it('We test getGzipSettings', () => { + const output = getGzipSetting(); + expect(output).to.be.a('boolean'); + }); + + describe('isBidRequestValid()', () => { + let bannerBid; + beforeEach(() => { + bannerBid = { + bidder: 'nexx360', + 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('We verify isBidRequestValid with unvalid adUnitName', () => { + bannerBid.params = { adUnitName: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with empty adUnitName', () => { + bannerBid.params = { adUnitName: '' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid adUnitPath', () => { + bannerBid.params = { adUnitPath: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid divId', () => { + bannerBid.params = { divId: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid unvalid allBids', () => { + bannerBid.params = { allBids: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with uncorrect tagid', () => { + bannerBid.params = { 'tagid': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with correct tagId', () => { + bannerBid.params = { 'tagId': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + + it('We verify isBidRequestValid with correct placement', () => { + bannerBid.params = { 'placement': 'testad' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + }); + + describe('getInsurAdsLocalStorage disabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => false); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getInsurAdsLocalStorage enabled but nothing', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => null); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(typeof output.nexx360Id).to.be.eql('string'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getInsurAdsLocalStorage enabled but wrong payload', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getInsurAdsLocalStorage enabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the nexx360Id', () => { + const output = getInsurAdsLocalStorage(); + expect(output.nexx360Id).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data not set', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => null); + }); + it('We test if we get the amxId', () => { + const output = getAmxId(STORAGE, 'nexx360'); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data set', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => 'abcdef'); + }); + it('We test if we get the amxId', () => { + const output = getAmxId(STORAGE, 'nexx360'); + expect(output).to.be.eql('abcdef'); + }); + after(() => { + sandbox.restore() + }); + }); + + 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 }; } + }); + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => 'abcdef'); + }); + describe('We test with a multiple display bids', () => { + const sampleBids = [ + { + bidder: 'nexx360', + params: { + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + }, + ortb2Imp: { + ext: { + gpid: '/12345/nexx360/Homepage/HP/Header-Ad', + } + }, + 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: 'nexx360', + params: { + placement: 'testPlacement', + allBids: true, + }, + 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: 'nexx360', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + bidderRequestId: '359bf8a3c06b2e', + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + topmostLocation: 'https://test.nexx360.io/adapter/index.html', + location: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null, + page: 'https://test.nexx360.io/adapter/index.html', + domain: 'test.nexx360.io', + ref: null, + legacy: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + referer: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null + }, + }, + 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('We perform a test with 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', + gpid: '/12345/nexx360/Homepage/HP/Header-Ad', + divId: 'div-1', + dimensions: { + slotW: 200, + slotH: 250, + cssMaxW: '400px', + cssMaxH: '350px', + }, + nexx360: { + tagId: 'luvxjvgn', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + divId: 'div-1', + }, + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + }, + }, + { + 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', + divId: 'div-2-abcd', + nexx360: { + placement: 'testPlacement', + divId: 'div-2-abcd', + allBids: true, + }, + }, + }, + ], + id: requestContent.id, + test: 0, + ext: { + version: requestContent.ext.version, + source: 'prebid.js', + pageViewId: requestContent.ext.pageViewId, + bidderVersion: '7.1', + localStorage: { amxId: 'abcdef'}, + sessionId: requestContent.ext.sessionId, + requestCounter: 0, + }, + cur: [ + 'USD', + ], + user: { + ext: { + eids: [ + { + source: 'amxdt.net', + uids: [ + { + id: 'abcdef', + atype: 1, + } + ] + } + ] + } + }, + }; + expect(requestContent).to.be.eql(expectedRequest); + }); + + if (FEATURES.VIDEO) { + it('We perform a test with a 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('We perform a test with a 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('We test interpretResponse', () => { + 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', + divId: 'div-1', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutut = [{ + requestId: '4ce809b61a3928', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + divId: 'div-1', + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + renderer: output[0].renderer, + }]; + expect(output).to.eql(expectedOutut); + }); + + 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 faf56517efca588689f1be112d36803d0b8eaa77 Mon Sep 17 00:00:00 2001 From: morock <98119227+mo4rock@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:38:38 +0200 Subject: [PATCH 060/105] LeagueM BId Adapter: initial release (#14479) * LeagueM BId Adapter: initial release * Update test/spec/modules/leagueMBidAdapter_spec.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/spec/modules/leagueMBidAdapter_spec.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/spec/modules/leagueMBidAdapter_spec.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/leagueMBidAdapter.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Patrick McCann --- modules/leagueMBidAdapter.js | 17 + modules/leagueMBidAdapter.md | 54 +++ test/spec/modules/leagueMBidAdapter_spec.js | 441 ++++++++++++++++++++ 3 files changed, 512 insertions(+) create mode 100644 modules/leagueMBidAdapter.js create mode 100644 modules/leagueMBidAdapter.md create mode 100644 test/spec/modules/leagueMBidAdapter_spec.js diff --git a/modules/leagueMBidAdapter.js b/modules/leagueMBidAdapter.js new file mode 100644 index 00000000000..3a0f1cdbebb --- /dev/null +++ b/modules/leagueMBidAdapter.js @@ -0,0 +1,17 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'leagueM'; +const ENDPOINT = 'https://pbjs.league-m.media'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: bid => isBidRequestValid(bid, ['pid']), + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/leagueMBidAdapter.md b/modules/leagueMBidAdapter.md new file mode 100644 index 00000000000..5ef96b0d66b --- /dev/null +++ b/modules/leagueMBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: LeagueM Bidder Adapter +Module Type: LeagueM Bidder Adapter +Maintainer: prebid@league-m.com +``` + +# Description + +Module that connects to league-m.media demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'leagueM', + params: { + env: 'leagueM', + pid: 'aa8217e20131c095fe9dba67981040b0', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skippable: true + } + }, + bids: [{ + bidder: 'leagueM', + params: { + env: 'leagueM', + pid: 'aa8217e20131c095fe9dba67981040b0', + ext: {} + } + }] + } +]; +``` diff --git a/test/spec/modules/leagueMBidAdapter_spec.js b/test/spec/modules/leagueMBidAdapter_spec.js new file mode 100644 index 00000000000..4c0f9a53529 --- /dev/null +++ b/test/spec/modules/leagueMBidAdapter_spec.js @@ -0,0 +1,441 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/leagueMBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; + +const ENDPOINT = 'https://pbjs.league-m.media'; + +const defaultRequest = { + tmax: 0, + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'leagueM', + params: { + pid: 'aa8217e20131c095fe9dba67981040b0', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skippable: true + } +}; + +const videoBidderRequest = { + bidderCode: 'leagueM', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'leagueM', + bids: [{bidId: 'qwerty'}] +}; + +describe('leagueMBidAdapter', () => { + describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required pid param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.pid; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('tmax').and.to.equal(defaultRequest.tmax); + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprConsent').and.to.deep.equal({}); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + pid: 'aa8217e20131c095fe9dba67981040b0' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + const bidderRequest = { + ortb2: { + source: { + ext: { + schain: { + ver: '1.0' + } + } + } + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], bidderRequest).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + ver: '1.0' + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skippable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); + }); + + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['leagueM'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['leagueM']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('should handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = {getFloor: 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = {getFloor: () => 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({floor: 'string', currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}); From bcd5f8b3f023a61b9c2a489c96541315e9918582 Mon Sep 17 00:00:00 2001 From: markappmedia Date: Fri, 20 Feb 2026 21:39:20 +0200 Subject: [PATCH 061/105] New Adapter: Harion (#14398) * new adapter harion * refactor(adapter): guard added to interpretResponse --- modules/harionBidAdapter.js | 25 ++ modules/harionBidAdapter.md | 79 ++++ test/spec/modules/harionBidAdapter_spec.js | 475 +++++++++++++++++++++ 3 files changed, 579 insertions(+) create mode 100644 modules/harionBidAdapter.js create mode 100644 modules/harionBidAdapter.md create mode 100644 test/spec/modules/harionBidAdapter_spec.js diff --git a/modules/harionBidAdapter.js b/modules/harionBidAdapter.js new file mode 100644 index 00000000000..40b1b8282b3 --- /dev/null +++ b/modules/harionBidAdapter.js @@ -0,0 +1,25 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'harion'; +const GVLID = 1406; +const AD_URL = 'https://east-api.harion-ma.com/pbjs'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse: (serverResponse) => { + if (!serverResponse || !Array.isArray(serverResponse.body)) { + return []; + } + + return interpretResponse(serverResponse); + } +}; + +registerBidder(spec); diff --git a/modules/harionBidAdapter.md b/modules/harionBidAdapter.md new file mode 100644 index 00000000000..a2ec196eeab --- /dev/null +++ b/modules/harionBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Harion Bidder Adapter +Module Type: Harion Bidder Adapter +Maintainer: adtech@markappmedia.site +``` + +# Description + +Connects to Harion exchange for bids. +Harion bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'harion', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'harion', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'harion', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/harionBidAdapter_spec.js b/test/spec/modules/harionBidAdapter_spec.js new file mode 100644 index 00000000000..d5a1dd509a6 --- /dev/null +++ b/test/spec/modules/harionBidAdapter_spec.js @@ -0,0 +1,475 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/harionBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'Harion'; + +describe('HarionBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK', + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + const serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + const serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 31d34c2663afe42b932683dc4362fe79b73a580d Mon Sep 17 00:00:00 2001 From: mkomorski Date: Fri, 20 Feb 2026 20:55:45 +0100 Subject: [PATCH 062/105] Core: disabling fingerprinting apis (#14404) * Core: disabling fingerprinting apis * getFallbackWindow -> utils --- .../devicePixelRatio/devicePixelRatio.js | 14 ++--- libraries/fingerprinting/fingerprinting.js | 12 ++++ libraries/timezone/timezone.js | 5 ++ libraries/webdriver/webdriver.js | 48 +++++++++++++-- modules/51DegreesRtdProvider.js | 3 +- modules/datablocksBidAdapter.js | 34 +---------- src/config.ts | 5 ++ src/utils.js | 12 ++++ test/spec/fingerprinting_spec.js | 60 +++++++++++++++++++ 9 files changed, 147 insertions(+), 46 deletions(-) create mode 100644 libraries/fingerprinting/fingerprinting.js create mode 100644 test/spec/fingerprinting_spec.js diff --git a/libraries/devicePixelRatio/devicePixelRatio.js b/libraries/devicePixelRatio/devicePixelRatio.js index 8bfe23bcb3d..c206ed053b3 100644 --- a/libraries/devicePixelRatio/devicePixelRatio.js +++ b/libraries/devicePixelRatio/devicePixelRatio.js @@ -1,14 +1,10 @@ -import {canAccessWindowTop, internal as utilsInternals} from '../../src/utils.js'; - -function getFallbackWindow(win) { - if (win) { - return win; - } - - return canAccessWindowTop() ? utilsInternals.getWindowTop() : utilsInternals.getWindowSelf(); -} +import {isFingerprintingApiDisabled} from '../fingerprinting/fingerprinting.js'; +import {getFallbackWindow} from '../../src/utils.js'; export function getDevicePixelRatio(win) { + if (isFingerprintingApiDisabled('devicepixelratio')) { + return 1; + } try { return getFallbackWindow(win).devicePixelRatio; } catch (e) { diff --git a/libraries/fingerprinting/fingerprinting.js b/libraries/fingerprinting/fingerprinting.js new file mode 100644 index 00000000000..aba529d0c8f --- /dev/null +++ b/libraries/fingerprinting/fingerprinting.js @@ -0,0 +1,12 @@ +import {config} from '../../src/config.js'; + +/** + * Returns true if the given fingerprinting API is disabled via setConfig({ disableFingerprintingApis: [...] }). + * Comparison is case-insensitive. Use for 'devicepixelratio', 'webdriver', 'resolvedoptions', 'screen'. + * @param {string} apiName + * @returns {boolean} + */ +export function isFingerprintingApiDisabled(apiName) { + const list = config.getConfig('disableFingerprintingApis'); + return Array.isArray(list) && list.some((item) => String(item).toLowerCase() === apiName.toLowerCase()); +} diff --git a/libraries/timezone/timezone.js b/libraries/timezone/timezone.js index e4ef39f28ef..8c09295d70f 100644 --- a/libraries/timezone/timezone.js +++ b/libraries/timezone/timezone.js @@ -1,3 +1,8 @@ +import {isFingerprintingApiDisabled} from '../fingerprinting/fingerprinting.js'; + export function getTimeZone() { + if (isFingerprintingApiDisabled('resolvedoptions')) { + return ''; + } return Intl.DateTimeFormat().resolvedOptions().timeZone; } diff --git a/libraries/webdriver/webdriver.js b/libraries/webdriver/webdriver.js index 957fea62ed8..8e10e4d0374 100644 --- a/libraries/webdriver/webdriver.js +++ b/libraries/webdriver/webdriver.js @@ -1,9 +1,49 @@ -import {canAccessWindowTop, internal as utilsInternals} from '../../src/utils.js'; +import {isFingerprintingApiDisabled} from '../fingerprinting/fingerprinting.js'; +import {getFallbackWindow} from '../../src/utils.js'; /** * Warning: accessing navigator.webdriver may impact fingerprinting scores when this API is included in the built script. + * @param {Window} [win] Window to check (defaults to top or self) + * @returns {boolean} */ -export function isWebdriverEnabled() { - const win = canAccessWindowTop() ? utilsInternals.getWindowTop() : utilsInternals.getWindowSelf(); - return win.navigator?.webdriver === true; +export function isWebdriverEnabled(win) { + if (isFingerprintingApiDisabled('webdriver')) { + return false; + } + return getFallbackWindow(win).navigator?.webdriver === true; +} + +/** + * Detects Selenium/WebDriver via document/window properties (e.g. __webdriver_script_fn, attributes). + * @param {Window} [win] Window to check + * @param {Document} [doc] Document to check (defaults to win.document) + * @returns {boolean} + */ +export function isSeleniumDetected(win, doc) { + if (isFingerprintingApiDisabled('webdriver')) { + return false; + } + const _win = win || (typeof window !== 'undefined' ? window : undefined); + const _doc = doc || (_win?.document); + if (!_win || !_doc) return false; + const checks = [ + 'webdriver' in _win, + '_Selenium_IDE_Recorder' in _win, + 'callSelenium' in _win, + '_selenium' in _win, + '__webdriver_script_fn' in _doc, + '__driver_evaluate' in _doc, + '__webdriver_evaluate' in _doc, + '__selenium_evaluate' in _doc, + '__fxdriver_evaluate' in _doc, + '__driver_unwrapped' in _doc, + '__webdriver_unwrapped' in _doc, + '__selenium_unwrapped' in _doc, + '__fxdriver_unwrapped' in _doc, + '__webdriver_script_func' in _doc, + _doc.documentElement?.getAttribute('selenium') !== null, + _doc.documentElement?.getAttribute('webdriver') !== null, + _doc.documentElement?.getAttribute('driver') !== null + ]; + return checks.some(Boolean); } diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js index f5c76357ffc..5c07511a280 100644 --- a/modules/51DegreesRtdProvider.js +++ b/modules/51DegreesRtdProvider.js @@ -8,6 +8,7 @@ import { mergeDeep, prefixLog, } from '../src/utils.js'; +import {getDevicePixelRatio} from '../libraries/devicePixelRatio/devicePixelRatio.js'; const MODULE_NAME = '51Degrees'; export const LOG_PREFIX = `[${MODULE_NAME} RTD Submodule]:`; @@ -126,7 +127,7 @@ export const get51DegreesJSURL = (pathData, win) => { ); deepSetNotEmptyValue(qs, '51D_ScreenPixelsHeight', _window?.screen?.height); deepSetNotEmptyValue(qs, '51D_ScreenPixelsWidth', _window?.screen?.width); - deepSetNotEmptyValue(qs, '51D_PixelRatio', _window?.devicePixelRatio); + deepSetNotEmptyValue(qs, '51D_PixelRatio', getDevicePixelRatio(_window)); const _qs = formatQS(qs); const _qsString = _qs ? `${queryPrefix}${_qs}` : ''; diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 7f5a4bedd62..25ded85bf05 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -7,7 +7,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; -import {isWebdriverEnabled} from '../libraries/webdriver/webdriver.js'; +import {isWebdriverEnabled, isSeleniumDetected} from '../libraries/webdriver/webdriver.js'; import { buildNativeRequest, parseNativeResponse } from '../libraries/nativeAssetsUtils.js'; export const storage = getStorageManager({bidderCode: 'datablocks'}); @@ -432,37 +432,7 @@ export class BotClientTests { }, selenium: function () { - let response = false; - - if (window && document) { - const results = [ - 'webdriver' in window, - '_Selenium_IDE_Recorder' in window, - 'callSelenium' in window, - '_selenium' in window, - '__webdriver_script_fn' in document, - '__driver_evaluate' in document, - '__webdriver_evaluate' in document, - '__selenium_evaluate' in document, - '__fxdriver_evaluate' in document, - '__driver_unwrapped' in document, - '__webdriver_unwrapped' in document, - '__selenium_unwrapped' in document, - '__fxdriver_unwrapped' in document, - '__webdriver_script_func' in document, - document.documentElement.getAttribute('selenium') !== null, - document.documentElement.getAttribute('webdriver') !== null, - document.documentElement.getAttribute('driver') !== null - ]; - - results.forEach(result => { - if (result === true) { - response = true; - } - }) - } - - return response; + return isSeleniumDetected(window, document); }, } } diff --git a/src/config.ts b/src/config.ts index 96f0427ea07..d373876b37b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -254,6 +254,11 @@ export interface Config { * https://docs.prebid.org/features/firstPartyData.html */ ortb2?: DeepPartial; + /** + * 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'. + */ + disableFingerprintingApis?: Array<'devicepixelratio' | 'webdriver' | 'resolvedoptions'>; } type PartialConfig = Partial & { [setting: string]: unknown }; diff --git a/src/utils.js b/src/utils.js index ec30b934a49..187e46c24c1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -206,6 +206,18 @@ export function canAccessWindowTop() { } } +/** + * Returns the window to use for fingerprinting reads: win if provided, otherwise top or self. + * @param {Window} [win] + * @returns {Window} + */ +export function getFallbackWindow(win) { + if (win) { + return win; + } + return canAccessWindowTop() ? internal.getWindowTop() : internal.getWindowSelf(); +} + /** * Wrappers to console.(log | info | warn | error). Takes N arguments, the same as the native methods */ diff --git a/test/spec/fingerprinting_spec.js b/test/spec/fingerprinting_spec.js new file mode 100644 index 00000000000..f1a810fc751 --- /dev/null +++ b/test/spec/fingerprinting_spec.js @@ -0,0 +1,60 @@ +import { expect } from 'chai'; + +import { config } from 'src/config.js'; +import { getDevicePixelRatio } from 'libraries/devicePixelRatio/devicePixelRatio.js'; +import { isWebdriverEnabled } from 'libraries/webdriver/webdriver.js'; +import { getTimeZone } from 'libraries/timezone/timezone.js'; + +describe('disableFingerprintingApis', function () { + after(function () { + config.resetConfig(); + }); + + it('when devicepixelratio is disabled, getDevicePixelRatio returns 1 without reading window.devicePixelRatio', function () { + const devicePixelRatioSpy = sinon.spy(); + const mockWin = { + get devicePixelRatio() { + devicePixelRatioSpy(); + return 2; + } + }; + config.setConfig({ disableFingerprintingApis: ['devicepixelratio'] }); + const result = getDevicePixelRatio(mockWin); + expect(result).to.equal(1); + sinon.assert.notCalled(devicePixelRatioSpy); + }); + + it('when webdriver is disabled, isWebdriverEnabled returns false without reading navigator.webdriver', function () { + const webdriverSpy = sinon.spy(); + const mockWin = { + navigator: { + get webdriver() { + webdriverSpy(); + return true; + } + } + }; + config.setConfig({ disableFingerprintingApis: ['webdriver'] }); + const result = isWebdriverEnabled(mockWin); + expect(result).to.equal(false); + sinon.assert.notCalled(webdriverSpy); + }); + + it('when resolvedoptions is disabled, getTimeZone returns safe default without calling Intl.DateTimeFormat', function () { + const resolvedOptionsSpy = sinon.spy(); + const dateTimeFormatStub = sinon.stub(Intl, 'DateTimeFormat').returns({ + resolvedOptions: function () { + resolvedOptionsSpy(); + return { timeZone: 'America/New_York' }; + } + }); + try { + config.setConfig({ disableFingerprintingApis: ['resolvedoptions'] }); + const result = getTimeZone(); + expect(result).to.equal(''); + sinon.assert.notCalled(resolvedOptionsSpy); + } finally { + dateTimeFormatStub.restore(); + } + }); +}); From 326de7b2c6127fa5e55b9d45423bd5518884a78d Mon Sep 17 00:00:00 2001 From: Vedant Madane <6527493+VedantMadane@users.noreply.github.com> Date: Sat, 21 Feb 2026 16:29:07 +0530 Subject: [PATCH 063/105] Fix several typos in comments and tests (#14498) --- modules/adtrueBidAdapter.js | 2 +- test/spec/modules/mgidRtdProvider_spec.js | 2 +- test/spec/modules/rakutenBidAdapter_spec.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 2 +- test/spec/modules/theAdxBidAdapter_spec.js | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 2c4278b9fb8..d8759bcbda6 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -400,7 +400,7 @@ function _createImpressionObject(bid, conf) { } } else { // mediaTypes is not present, so this is a banner only impression - // this part of code is required for older testcases with no 'mediaTypes' to run succesfully. + // this part of code is required for older testcases with no 'mediaTypes' to run successfully. bannerObj = { pos: 0, w: bid.params.width, diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js index 7fd41a3c4c5..b3f160b1530 100644 --- a/test/spec/modules/mgidRtdProvider_spec.js +++ b/test/spec/modules/mgidRtdProvider_spec.js @@ -33,7 +33,7 @@ describe('Mgid RTD submodule', () => { expect(mgidSubmodule.init({})).to.be.false; }); - it('getBidRequestData send all params to our endpoint and succesfully modifies ortb2', () => { + it('getBidRequestData send all params to our endpoint and successfully modifies ortb2', () => { const responseObj = { userSegments: ['100', '200'], userSegtax: 5, diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js index e6cdb12e31d..9b3dd70f1a6 100644 --- a/test/spec/modules/rakutenBidAdapter_spec.js +++ b/test/spec/modules/rakutenBidAdapter_spec.js @@ -161,7 +161,7 @@ describe('rakutenBidAdapter', function() { pixelEnabled: true } }); - it('sucess usersync url', function () { + it('success usersync url', function () { const result = []; result.push({type: 'image', url: 'https://rdn1.test/sync?uid=9876543210'}); result.push({type: 'image', url: 'https://rdn2.test/sync?uid=9876543210'}); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index bd9990f75de..831f349cef5 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1815,7 +1815,7 @@ describe('the rubicon adapter', function () { }) }) - it('should send gpid and pbadslot since it is prefered over dfp code', function () { + it('should send gpid and pbadslot since it is preferred over dfp code', function () { bidderRequest.bids[0].ortb2Imp = { ext: { gpid: '/1233/sports&div1', diff --git a/test/spec/modules/theAdxBidAdapter_spec.js b/test/spec/modules/theAdxBidAdapter_spec.js index fd2a306ca05..2b531b6b10b 100644 --- a/test/spec/modules/theAdxBidAdapter_spec.js +++ b/test/spec/modules/theAdxBidAdapter_spec.js @@ -416,7 +416,7 @@ describe('TheAdxAdapter', function () { expect(result).to.eql([]); }); - it('returns a valid bid response on sucessful banner request', function () { + it('returns a valid bid response on successful banner request', function () { const incomingRequestId = 'XXtestingXX'; const responsePrice = 3.14 @@ -484,7 +484,7 @@ describe('TheAdxAdapter', function () { expect(processedBid.currency).to.equal(responseCurrency); }); - it('returns a valid deal bid response on sucessful banner request with deal', function () { + it('returns a valid deal bid response on successful banner request with deal', function () { const incomingRequestId = 'XXtestingXX'; const responsePrice = 3.14 @@ -556,7 +556,7 @@ describe('TheAdxAdapter', function () { expect(processedBid.dealId).to.equal(dealId); }); - it('returns an valid bid response on sucessful video request', function () { + it('returns an valid bid response on successful video request', function () { const incomingRequestId = 'XXtesting-275XX'; const responsePrice = 6 const vast_url = 'https://theadx.com/vast?rid=a8ae0b48-a8db-4220-ba0c-7458f452b1f5&{FOR_COVARAGE}' @@ -622,7 +622,7 @@ describe('TheAdxAdapter', function () { expect(processedBid.vastUrl).to.equal(vast_url); }); - it('returns an valid bid response on sucessful native request', function () { + it('returns an valid bid response on successful native request', function () { const incomingRequestId = 'XXtesting-275XX'; const responsePrice = 6 const nurl = 'https://app.theadx.com/ixc?rid=02aefd80-2df9-11e9-896d-d33384d77f5c&time=v-1549888312715&sp=1WzMjcRpeyk%3D'; From 7abc65efcf7145eb81deb59ca53a901faf5c5a0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:37:14 -0500 Subject: [PATCH 064/105] Bump ajv from 6.12.6 to 6.14.0 (#14499) Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0. - [Release notes](https://github.com/ajv-validator/ajv/releases) - [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0) --- updated-dependencies: - dependency-name: ajv dependency-version: 6.14.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9f002257af..7ebcabde344 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6533,11 +6533,10 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6566,9 +6565,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18765,9 +18764,9 @@ } }, "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -26481,9 +26480,9 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -26501,9 +26500,9 @@ }, "dependencies": { "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -34365,9 +34364,9 @@ }, "dependencies": { "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", From cbb723da6464cecb4f26a0d1a37b3461de797f49 Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:07:37 +0530 Subject: [PATCH 065/105] Setting alwaysHasCapacity flag to true (#14500) --- modules/pubmaticBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index f23665670e9..5051eb8e6ee 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -756,6 +756,7 @@ export const spec = { code: BIDDER_CODE, gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], + alwaysHasCapacity: true, /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * From 6485feb31b82fae89bab35bdfe6722da985afcfc Mon Sep 17 00:00:00 2001 From: adclusterdev Date: Mon, 23 Feb 2026 17:38:42 +0300 Subject: [PATCH 066/105] Adcluster Bid Adapter: Support Adcluster (#14050) * adcluster - new adapter * fixes * video preview id change * video preview id change * Update adclusterBidAdapter_spec.js * fallback * multiformat fix --------- Co-authored-by: Patrick McCann Co-authored-by: Patrick McCann --- .../gpt/adcluster_banner_example.html | 115 +++++ .../gpt/adcluster_video_example.html | 291 ++++++++++++ modules/adclusterBidAdapter.js | 183 ++++++++ modules/adclusterBidAdapter.md | 46 ++ test/spec/modules/adclusterBidAdapter_spec.js | 415 ++++++++++++++++++ 5 files changed, 1050 insertions(+) create mode 100644 integrationExamples/gpt/adcluster_banner_example.html create mode 100644 integrationExamples/gpt/adcluster_video_example.html create mode 100644 modules/adclusterBidAdapter.js create mode 100644 modules/adclusterBidAdapter.md create mode 100644 test/spec/modules/adclusterBidAdapter_spec.js diff --git a/integrationExamples/gpt/adcluster_banner_example.html b/integrationExamples/gpt/adcluster_banner_example.html new file mode 100644 index 00000000000..4f7bf646bb9 --- /dev/null +++ b/integrationExamples/gpt/adcluster_banner_example.html @@ -0,0 +1,115 @@ + + + + + Adcluster Adapter Test + + + + + + + + + + +

Prebid.js Live Adapter Test

+
+ + + + diff --git a/integrationExamples/gpt/adcluster_video_example.html b/integrationExamples/gpt/adcluster_video_example.html new file mode 100644 index 00000000000..0a309b24749 --- /dev/null +++ b/integrationExamples/gpt/adcluster_video_example.html @@ -0,0 +1,291 @@ + + + + + Adcluster Adapter – Outstream Test with Fallback + + + + + + + + + +

Adcluster Adapter – Outstream Test (AN renderer + IMA fallback)

+
+ +
+ + + + diff --git a/modules/adclusterBidAdapter.js b/modules/adclusterBidAdapter.js new file mode 100644 index 00000000000..b8b1f248582 --- /dev/null +++ b/modules/adclusterBidAdapter.js @@ -0,0 +1,183 @@ +import { registerBidder } from "../src/adapters/bidderFactory.js"; +import { BANNER, VIDEO } from "../src/mediaTypes.js"; + +const BIDDER_CODE = "adcluster"; +const ENDPOINT = "https://core.adcluster.com.tr/bid"; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bid) { + return !!bid?.params?.unitId; + }, + + buildRequests(validBidRequests, bidderRequest) { + const _auctionId = bidderRequest.auctionId || ""; + const payload = { + bidderCode: bidderRequest.bidderCode, + auctionId: _auctionId, + bidderRequestId: bidderRequest.bidderRequestId, + bids: validBidRequests.map((b) => buildImp(b)), + auctionStart: bidderRequest.auctionStart, + timeout: bidderRequest.timeout, + start: bidderRequest.start, + regs: { ext: {} }, + user: { ext: {} }, + source: { ext: {} }, + }; + + // privacy + if (bidderRequest?.gdprConsent) { + payload.regs = payload.regs || { ext: {} }; + payload.regs.ext = payload.regs.ext || {}; + payload.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + payload.user.ext.consent = bidderRequest.gdprConsent.consentString || ""; + } + if (bidderRequest?.uspConsent) { + payload.regs = payload.regs || { ext: {} }; + payload.regs.ext.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest?.ortb2?.regs?.gpp) { + payload.regs = payload.regs || { ext: {} }; + payload.regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + payload.regs.ext.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + if (validBidRequests[0]?.userIdAsEids) { + payload.user.ext.eids = validBidRequests[0].userIdAsEids; + } + if (validBidRequests[0]?.ortb2?.source?.ext?.schain) { + payload.source.ext.schain = validBidRequests[0].ortb2.source.ext.schain; + } + + return { + method: "POST", + url: ENDPOINT, + data: payload, + options: { contentType: "text/plain" }, + }; + }, + + interpretResponse(serverResponse) { + const body = serverResponse?.body; + if (!body || !Array.isArray(body)) return []; + const bids = []; + + body.forEach((b) => { + const mediaType = detectMediaType(b); + const bid = { + requestId: b.requestId, + cpm: b.cpm, + currency: b.currency, + width: b.width, + height: b.height, + creativeId: b.creativeId, + ttl: b.ttl, + netRevenue: b.netRevenue, + meta: { + advertiserDomains: b.meta?.advertiserDomains || [], + }, + mediaType, + }; + + if (mediaType === BANNER) { + bid.ad = b.ad; + } + if (mediaType === VIDEO) { + bid.vastUrl = b.ad; + } + bids.push(bid); + }); + + return bids; + }, +}; + +/* ---------- helpers ---------- */ + +function buildImp(bid) { + const _transactionId = bid.transactionId || ""; + const _adUnitId = bid.adUnitId || ""; + const _auctionId = bid.auctionId || ""; + const imp = { + params: { + unitId: bid.params.unitId, + }, + bidId: bid.bidId, + bidderRequestId: bid.bidderRequestId, + transactionId: _transactionId, + adUnitId: _adUnitId, + auctionId: _auctionId, + ext: { + floors: getFloorsAny(bid), + }, + }; + + if (bid.params && bid.params.previewMediaId) { + imp.params.previewMediaId = bid.params.previewMediaId; + } + + const mt = bid.mediaTypes || {}; + + // BANNER + if (mt.banner?.sizes?.length) { + imp.width = mt.banner.sizes[0] && mt.banner.sizes[0][0]; + imp.height = mt.banner.sizes[0] && mt.banner.sizes[0][1]; + } + if (mt.video) { + const v = mt.video; + const playerSize = toSizeArray(v.playerSize); + const [vw, vh] = playerSize?.[0] || []; + imp.width = vw; + imp.height = vh; + imp.video = { + minduration: v.minduration || 1, + maxduration: v.maxduration || 120, + ext: { + context: v.context || "instream", + floor: getFloors(bid, "video", playerSize?.[0]), + }, + }; + } + + return imp; +} + +function toSizeArray(s) { + if (!s) return null; + // playerSize can be [w,h] or [[w,h], [w2,h2]] + return Array.isArray(s[0]) ? s : [s]; +} + +function getFloors(bid, mediaType = "banner", size) { + try { + if (!bid.getFloor) return null; + // size can be [w,h] or '*' + const sz = Array.isArray(size) ? size : "*"; + const res = bid.getFloor({ mediaType, size: sz }); + return res && typeof res.floor === "number" ? res.floor : null; + } catch { + return null; + } +} + +function detectMediaType(bid) { + if (bid.mediaType === "video") return VIDEO; + else return BANNER; +} + +function getFloorsAny(bid) { + // Try to collect floors per type + const out = {}; + const mt = bid.mediaTypes || {}; + if (mt.banner) { + out.banner = getFloors(bid, "banner", "*"); + } + if (mt.video) { + const ps = toSizeArray(mt.video.playerSize); + out.video = getFloors(bid, "video", (ps && ps[0]) || "*"); + } + return out; +} + +registerBidder(spec); diff --git a/modules/adclusterBidAdapter.md b/modules/adclusterBidAdapter.md new file mode 100644 index 00000000000..59300e2c857 --- /dev/null +++ b/modules/adclusterBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +**Module Name**: Adcluster Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: dev@adcluster.com.tr + +# Description + +Prebid.js bidder adapter module for connecting to Adcluster. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'adcluster-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'adcluster', + params: { + unitId: '42d1f525-5792-47a6-846d-1825e53c97d6', + previewMediaId: "b4dbc48c-0b90-4628-bc55-f46322b89b63", + }, + }] + }, + { + code: 'adcluster-video', + mediaTypes: { + video: { + playerSize: [[640, 480]], + } + }, + bids: [{ + bidder: 'adcluster', + params: { + unitId: "37dd91b2-049d-4027-94b9-d63760fc10d3", + previewMediaId: "133b7dc9-bb6e-4ab2-8f95-b796cf19f27e", + }, + }] + } +]; +``` diff --git a/test/spec/modules/adclusterBidAdapter_spec.js b/test/spec/modules/adclusterBidAdapter_spec.js new file mode 100644 index 00000000000..d068a0b5944 --- /dev/null +++ b/test/spec/modules/adclusterBidAdapter_spec.js @@ -0,0 +1,415 @@ +// test/spec/modules/adclusterBidAdapter_spec.js + +import { expect } from "chai"; +import { spec } from "modules/adclusterBidAdapter.js"; // adjust path if needed +import { BANNER, VIDEO } from "src/mediaTypes.js"; + +const BIDDER_CODE = "adcluster"; +const ENDPOINT = "https://core.adcluster.com.tr/bid"; + +describe("adclusterBidAdapter", function () { + // ---------- Test Fixtures (immutable) ---------- + const baseBid = Object.freeze({ + bidder: BIDDER_CODE, + bidId: "2f5d", + bidderRequestId: "breq-1", + auctionId: "auc-1", + transactionId: "txn-1", + adUnitCode: "div-1", + adUnitId: "adunit-1", + params: { unitId: "61884b5c-9420-4f15-871f-2dcc2fa1cff5" }, + }); + + const gdprConsent = Object.freeze({ + gdprApplies: true, + consentString: "BOJ/P2HOJ/P2HABABMAAAAAZ+A==", + }); + + const uspConsent = "1---"; + const gpp = "DBABLA.."; + const gppSid = [7, 8]; + + const bidderRequestBase = Object.freeze({ + auctionId: "auc-1", + bidderCode: BIDDER_CODE, + bidderRequestId: "breq-1", + auctionStart: 1111111111111, + timeout: 2000, + start: 1111111111112, + ortb2: { regs: { gpp, gpp_sid: gppSid } }, + gdprConsent, + uspConsent, + }); + + // helpers return fresh objects to avoid cross-test mutation + function mkBidBanner(extra = {}) { + return { + ...baseBid, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + getFloor: ({ mediaType }) => { + if (mediaType === "banner") return { currency: "USD", floor: 0.5 }; + return { currency: "USD", floor: 0.0 }; + }, + userIdAsEids: [ + { source: "example.com", uids: [{ id: "abc", atype: 1 }] }, + ], + ortb2: { + source: { ext: { schain: { ver: "1.0", complete: 1, nodes: [] } } }, + }, + ...extra, + }; + } + + function mkBidVideo(extra = {}) { + return { + ...baseBid, + mediaTypes: { + video: { + context: "instream", + playerSize: [640, 360], + minduration: 5, + maxduration: 30, + }, + }, + getFloor: ({ mediaType, size }) => { + if (mediaType === "video" && Array.isArray(size)) { + return { currency: "USD", floor: 1.2 }; + } + return { currency: "USD", floor: 0.0 }; + }, + userIdAsEids: [ + { source: "example.com", uids: [{ id: "xyz", atype: 1 }] }, + ], + ortb2: { + source: { ext: { schain: { ver: "1.0", complete: 1, nodes: [] } } }, + }, + ...extra, + }; + } + + describe("isBidRequestValid", function () { + it("returns true when params.unitId is present", function () { + // Arrange + const bid = { ...baseBid }; + + // Act + const valid = spec.isBidRequestValid(bid); + + // Assert + expect(valid).to.equal(true); + }); + + it("returns false when params.unitId is missing", function () { + // Arrange + const bid = { ...baseBid, params: {} }; + + // Act + const valid = spec.isBidRequestValid(bid); + + // Assert + expect(valid).to.equal(false); + }); + + it("returns false when params is undefined", function () { + // Arrange + const bid = { ...baseBid, params: undefined }; + + // Act + const valid = spec.isBidRequestValid(bid); + + // Assert + expect(valid).to.equal(false); + }); + }); + + describe("buildRequests", function () { + it("builds a POST request with JSON body to the right endpoint", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [mkBidBanner(), mkBidVideo()]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + expect(req.method).to.equal("POST"); + expect(req.url).to.equal(ENDPOINT); + expect(req.options).to.deep.equal({ contentType: "text/plain" }); + expect(req.data).to.be.an("object"); + expect(req.data.bidderCode).to.equal(BIDDER_CODE); + expect(req.data.auctionId).to.equal(br.auctionId); + expect(req.data.bids).to.be.an("array").with.length(2); + }); + + it("includes privacy signals (GDPR, USP, GPP) when present", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [mkBidBanner()]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + const { regs, user } = req.data; + expect(regs).to.be.an("object"); + expect(regs.ext.gdpr).to.equal(1); + expect(user.ext.consent).to.equal(gdprConsent.consentString); + expect(regs.ext.us_privacy).to.equal(uspConsent); + expect(regs.ext.gpp).to.equal(gpp); + expect(regs.ext.gppSid).to.deep.equal(gppSid); + }); + + it("omits privacy fields when not provided", function () { + // Arrange + const minimalBR = { + auctionId: "auc-2", + bidderCode: BIDDER_CODE, + bidderRequestId: "breq-2", + auctionStart: 1, + timeout: 1000, + start: 2, + }; + const bids = [mkBidBanner()]; + + // Act + const req = spec.buildRequests(bids, minimalBR); + + // Assert + // regs.ext should exist but contain no privacy flags + expect(req.data.regs).to.be.an("object"); + expect(req.data.regs.ext).to.deep.equal({}); + // user.ext.consent must be undefined when no GDPR + expect(req.data.user).to.be.an("object"); + expect(req.data.user.ext).to.be.an("object"); + expect(req.data.user.ext.consent).to.be.undefined; + // allow eids to be present (they come from bids) + // don't assert deep-equality on user.ext, just ensure no privacy fields + expect(req.data.user.ext.gdpr).to.be.undefined; + }); + + it("passes userIdAsEids and schain when provided", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [mkBidBanner()]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + expect(req.data.user.ext.eids).to.be.an("array").with.length(1); + expect(req.data.source.ext.schain).to.be.an("object"); + }); + + it("sets banner dimensions from first size and includes floors ext", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [mkBidBanner()]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + const imp = req.data.bids[0]; + expect(imp.width).to.equal(300); + expect(imp.height).to.equal(250); + expect(imp.ext).to.have.property("floors"); + expect(imp.ext.floors.banner).to.equal(0.5); + }); + + it("sets video sizes from playerSize and includes video floors", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [mkBidVideo()]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + const imp = req.data.bids[0]; + expect(imp.width).to.equal(640); + expect(imp.height).to.equal(360); + expect(imp.video).to.be.an("object"); + expect(imp.video.minduration).to.equal(5); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.ext.context).to.equal("instream"); + expect(imp.video.ext.floor).to.equal(1.2); + expect(imp.ext.floors.video).to.equal(1.2); + }); + + it("gracefully handles missing getFloor", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [mkBidBanner({ getFloor: undefined })]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + expect(req.data.bids[0].ext.floors.banner).to.equal(null); + }); + + it("passes previewMediaId when provided", function () { + // Arrange + const br = { ...bidderRequestBase }; + const bids = [ + mkBidVideo({ params: { unitId: "x", previewMediaId: "media-123" } }), + ]; + + // Act + const req = spec.buildRequests(bids, br); + + // Assert + expect(req.data.bids[0].params.previewMediaId).to.equal("media-123"); + }); + }); + + describe("interpretResponse", function () { + it("returns empty array when body is missing or not an array", function () { + // Arrange + const missing = { body: null }; + const notArray = { body: {} }; + + // Act + const out1 = spec.interpretResponse(missing); + const out2 = spec.interpretResponse(notArray); + + // Assert + expect(out1).to.deep.equal([]); + expect(out2).to.deep.equal([]); + }); + + it("maps banner responses to Prebid bids", function () { + // Arrange + const serverBody = [ + { + requestId: "2f5d", + cpm: 1.23, + currency: "USD", + width: 300, + height: 250, + creativeId: "cr-1", + ttl: 300, + netRevenue: true, + mediaType: "banner", + ad: "
creative
", + meta: { advertiserDomains: ["advertiser.com"] }, + }, + ]; + + // Act + const out = spec.interpretResponse({ body: serverBody }); + + // Assert + expect(out).to.have.length(1); + const b = out[0]; + expect(b.requestId).to.equal("2f5d"); + expect(b.cpm).to.equal(1.23); + expect(b.mediaType).to.equal(BANNER); + expect(b.ad).to.be.a("string"); + expect(b.meta.advertiserDomains).to.deep.equal(["advertiser.com"]); + }); + + it("maps video responses to Prebid bids (vastUrl)", function () { + // Arrange + const serverBody = [ + { + requestId: "vid-1", + cpm: 2.5, + currency: "USD", + width: 640, + height: 360, + creativeId: "cr-v", + ttl: 300, + netRevenue: true, + mediaType: "video", + ad: "https://vast.tag/url.xml", + meta: { advertiserDomains: ["brand.com"] }, // mediaType hint optional + }, + ]; + + // Act + const out = spec.interpretResponse({ body: serverBody }); + + // Assert + expect(out).to.have.length(1); + const b = out[0]; + expect(b.requestId).to.equal("vid-1"); + expect(b.mediaType).to.equal(VIDEO); + expect(b.vastUrl).to.equal("https://vast.tag/url.xml"); + expect(b.ad).to.be.undefined; + }); + + it("handles missing meta.advertiserDomains safely", function () { + // Arrange + const serverBody = [ + { + requestId: "2f5d", + cpm: 0.2, + currency: "USD", + width: 300, + height: 250, + creativeId: "cr-2", + ttl: 120, + netRevenue: true, + ad: "
", + meta: {}, + }, + ]; + + // Act + const out = spec.interpretResponse({ body: serverBody }); + + // Assert + expect(out[0].meta.advertiserDomains).to.deep.equal([]); + }); + + it("supports multiple mixed responses", function () { + // Arrange + const serverBody = [ + { + requestId: "b-1", + cpm: 0.8, + currency: "USD", + width: 300, + height: 250, + creativeId: "cr-b", + ttl: 300, + netRevenue: true, + ad: "
banner
", + mediaType: "banner", + meta: { advertiserDomains: [] }, + }, + { + requestId: "v-1", + cpm: 3.1, + currency: "USD", + width: 640, + height: 360, + creativeId: "cr-v", + ttl: 300, + netRevenue: true, + mediaType: "video", + ad: "https://vast.example/vast.xml", + meta: { advertiserDomains: ["x.com"] }, + }, + ]; + + // Act + const out = spec.interpretResponse({ body: serverBody }); + + // Assert + expect(out).to.have.length(2); + const [b, v] = out; + expect(b.mediaType).to.equal(BANNER); + expect(v.mediaType).to.equal(VIDEO); + expect(v.vastUrl).to.match(/^https:\/\/vast\.example/); + }); + }); +}); From abc8b82e2119dfba9af4fe5a6beca5eee3a7b2f0 Mon Sep 17 00:00:00 2001 From: v0idxyz <58184010+v0idxyz@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:59:10 +0100 Subject: [PATCH 067/105] ReVantage Bid Adapter: initial release (#14180) * Create revantageBidAdapter.js * Create revantageBidAdapter.md * Update revantageBidAdapter.js * Update revantageBidAdapter.js * Update revantageBidAdapter.js * Update revantageBidAdapter.js Fixed trailing slash Error on Line 123 * Create revantageBidAdapter_spec.js * Update revantageBidAdapter_spec.js Fixed Trailing slashes (again) * Update revantageBidAdapter_spec.js * Update revantageBidAdapter_spec.js same thing again * Refactor Revantage Bid Adapter for media types and bids Refactor Revantage Bid Adapter to use media type constants and improve bid response handling. * Refactor RevantageBidAdapter tests for GPP consent * Update modules/revantageBidAdapter.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/revantageBidAdapter.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Validate feedId consistency in batch bid requests Added validation to ensure all bid requests in a batch have the same feedId, logging a warning if they do not. * Add test for rejecting batch with different feedIds * Update syncOptions for image sync URL parameters * Update sync URL to use 'tag=img' instead of 'type=img' * Update print statement from 'Hello' to 'Goodbye' * fixed * Enhance video bid handling and add utility functions Added functions to handle video size extraction and VAST detection. --------- Co-authored-by: Patrick McCann Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- modules/revantageBidAdapter.js | 407 +++++++ modules/revantageBidAdapter.md | 34 + test/spec/modules/revantageBidAdapter_spec.js | 994 ++++++++++++++++++ 3 files changed, 1435 insertions(+) create mode 100644 modules/revantageBidAdapter.js create mode 100644 modules/revantageBidAdapter.md create mode 100644 test/spec/modules/revantageBidAdapter_spec.js diff --git a/modules/revantageBidAdapter.js b/modules/revantageBidAdapter.js new file mode 100644 index 00000000000..0a0186d5b59 --- /dev/null +++ b/modules/revantageBidAdapter.js @@ -0,0 +1,407 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone, deepAccess, logWarn, logError, triggerPixel } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'revantage'; +const ENDPOINT_URL = 'https://bid.revantage.io/bid'; +const SYNC_URL = 'https://sync.revantage.io/sync'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.feedId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + // Handle null/empty bid requests + if (!validBidRequests || validBidRequests.length === 0) { + return []; + } + + // All bid requests in a batch must have the same feedId + // If not, we log a warning and return an empty array + const feedId = validBidRequests[0]?.params?.feedId; + const allSameFeedId = validBidRequests.every(bid => bid.params.feedId === feedId); + if (!allSameFeedId) { + logWarn('Revantage: All bid requests in a batch must have the same feedId'); + return []; + } + + try { + const openRtbBidRequest = makeOpenRtbRequest(validBidRequests, bidderRequest); + return { + method: 'POST', + url: ENDPOINT_URL + '?feed=' + encodeURIComponent(feedId), + data: JSON.stringify(openRtbBidRequest), + options: { + contentType: 'text/plain', + withCredentials: false + }, + bidRequests: validBidRequests + }; + } catch (e) { + logError('Revantage: buildRequests failed', e); + return []; + } + }, + + interpretResponse: function(serverResponse, request) { + const bids = []; + const resp = serverResponse.body; + const originalBids = request.bidRequests || []; + const bidIdMap = {}; + originalBids.forEach(b => { bidIdMap[b.bidId] = b; }); + + if (!resp || !Array.isArray(resp.seatbid)) return bids; + + resp.seatbid.forEach(seatbid => { + if (Array.isArray(seatbid.bid)) { + seatbid.bid.forEach(rtbBid => { + const originalBid = bidIdMap[rtbBid.impid]; + if (!originalBid || !rtbBid.price || rtbBid.price <= 0) return; + + // Check for ad markup + const hasAdMarkup = !!(rtbBid.adm || rtbBid.vastXml || rtbBid.vastUrl); + if (!hasAdMarkup) { + logWarn('Revantage: No ad markup in bid'); + return; + } + + const bidResponse = { + requestId: originalBid.bidId, + cpm: rtbBid.price, + width: rtbBid.w || getFirstSize(originalBid, 0, 300), + height: rtbBid.h || getFirstSize(originalBid, 1, 250), + creativeId: rtbBid.crid || rtbBid.id || rtbBid.adid || 'revantage-' + Date.now(), + dealId: rtbBid.dealid, + currency: resp.cur || 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: rtbBid.adomain || [], + dsp: seatbid.seat || 'unknown', + networkName: 'Revantage' + } + }; + + // Add burl for server-side win notification + if (rtbBid.burl) { + bidResponse.burl = rtbBid.burl; + } + + // Determine if this is a video bid + // FIX: Check for VAST content in adm even for multi-format ad units + const isVideo = (rtbBid.ext && rtbBid.ext.mediaType === 'video') || + rtbBid.vastXml || rtbBid.vastUrl || + isVastAdm(rtbBid.adm) || + (originalBid.mediaTypes && originalBid.mediaTypes.video && + !originalBid.mediaTypes.banner); + + if (isVideo) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = rtbBid.vastXml || rtbBid.adm; + bidResponse.vastUrl = rtbBid.vastUrl; + + if (!bidResponse.vastUrl && !bidResponse.vastXml) { + logWarn('Revantage: Video bid missing VAST content'); + return; + } + } else { + bidResponse.mediaType = BANNER; + bidResponse.ad = rtbBid.adm; + + if (!bidResponse.ad) { + logWarn('Revantage: Banner bid missing ad markup'); + return; + } + } + + // Add DSP price if available + if (rtbBid.ext && rtbBid.ext.dspPrice) { + bidResponse.meta.dspPrice = rtbBid.ext.dspPrice; + } + + bids.push(bidResponse); + }); + } + }); + + return bids; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + let params = '?cb=' + new Date().getTime(); + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + } + if (typeof gdprConsent.consentString === 'string') { + params += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString); + } + } + + if (uspConsent && typeof uspConsent === 'string') { + params += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + if (gppConsent) { + if (gppConsent.gppString) { + params += '&gpp=' + encodeURIComponent(gppConsent.gppString); + } + if (gppConsent.applicableSections) { + params += '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(',')); + } + } + + if (syncOptions.iframeEnabled) { + syncs.push({ type: 'iframe', url: SYNC_URL + params }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ type: 'image', url: SYNC_URL + params + '&tag=img' }); + } + + return syncs; + }, + + onBidWon: function(bid) { + if (bid.burl) { + triggerPixel(bid.burl); + } + } +}; + +// === MAIN RTB BUILDER === +function makeOpenRtbRequest(validBidRequests, bidderRequest) { + const imp = validBidRequests.map(bid => { + const sizes = getSizes(bid); + const floor = getBidFloorEnhanced(bid); + + const impression = { + id: bid.bidId, + tagid: bid.adUnitCode, + bidfloor: floor, + ext: { + feedId: deepAccess(bid, 'params.feedId'), + bidder: { + placementId: deepAccess(bid, 'params.placementId'), + publisherId: deepAccess(bid, 'params.publisherId') + } + } + }; + + // Add banner specs + if (bid.mediaTypes && bid.mediaTypes.banner) { + impression.banner = { + w: sizes[0][0], + h: sizes[0][1], + format: sizes.map(size => ({ w: size[0], h: size[1] })) + }; + } + + // Add video specs + if (bid.mediaTypes && bid.mediaTypes.video) { + const video = bid.mediaTypes.video; + impression.video = { + mimes: video.mimes || ['video/mp4', 'video/webm'], + minduration: video.minduration || 0, + maxduration: video.maxduration || 60, + protocols: video.protocols || [2, 3, 5, 6], + w: getVideoSize(video.playerSize, 0, 640), + h: getVideoSize(video.playerSize, 1, 360), + placement: video.placement || 1, + playbackmethod: video.playbackmethod || [1, 2], + api: video.api || [1, 2], + skip: video.skip || 0, + skipmin: video.skipmin || 0, + skipafter: video.skipafter || 0, + pos: video.pos || 0, + startdelay: video.startdelay || 0, + linearity: video.linearity || 1 + }; + } + + return impression; + }); + + let user = {}; + if (validBidRequests[0] && validBidRequests[0].userIdAsEids) { + user.eids = deepClone(validBidRequests[0].userIdAsEids); + } + + const ortb2 = bidderRequest.ortb2 || {}; + const site = { + domain: typeof window !== 'undefined' ? window.location.hostname : '', + page: typeof window !== 'undefined' ? window.location.href : '', + ref: typeof document !== 'undefined' ? document.referrer : '' + }; + + // Merge ortb2 site data + if (ortb2.site) { + Object.assign(site, deepClone(ortb2.site)); + } + + const device = deepClone(ortb2.device) || {}; + // Add basic device info if not present + if (!device.ua) { + device.ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; + } + if (!device.language) { + device.language = typeof navigator !== 'undefined' ? navigator.language : ''; + } + if (!device.w) { + device.w = typeof screen !== 'undefined' ? screen.width : 0; + } + if (!device.h) { + device.h = typeof screen !== 'undefined' ? screen.height : 0; + } + if (!device.devicetype) { + device.devicetype = getDeviceType(); + } + + const regs = { ext: {} }; + if (bidderRequest.gdprConsent) { + regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + user.ext = { consent: bidderRequest.gdprConsent.consentString }; + } + if (bidderRequest.uspConsent) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // Add GPP consent + if (bidderRequest.gppConsent) { + if (bidderRequest.gppConsent.gppString) { + regs.ext.gpp = bidderRequest.gppConsent.gppString; + } + if (bidderRequest.gppConsent.applicableSections) { + // Send as array, not comma-separated string + regs.ext.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + } + + // Get supply chain + const schain = bidderRequest.schain || (validBidRequests[0] && validBidRequests[0].schain); + + return { + id: bidderRequest.auctionId, + imp: imp, + site: site, + device: device, + user: user, + regs: regs, + schain: schain, + tmax: bidderRequest.timeout || 1000, + cur: ['USD'], + ext: { + prebid: { + version: '$prebid.version$' + } + } + }; +} + +// === UTILS === +function getSizes(bid) { + if (bid.mediaTypes && bid.mediaTypes.banner && Array.isArray(bid.mediaTypes.banner.sizes) && bid.mediaTypes.banner.sizes.length > 0) { + return bid.mediaTypes.banner.sizes; + } + if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.playerSize && bid.mediaTypes.video.playerSize.length > 0) { + return bid.mediaTypes.video.playerSize; + } + if (bid.sizes && bid.sizes.length > 0) { + return bid.sizes; + } + return [[300, 250]]; +} + +function getFirstSize(bid, index, defaultVal) { + const sizes = getSizes(bid); + return (sizes && sizes[0] && sizes[0][index]) || defaultVal; +} + +/** + * Safely extract video dimensions from playerSize. + * Handles both nested [[640, 480]] and flat [640, 480] formats. + * @param {Array} playerSize - video.playerSize from mediaTypes config + * @param {number} index - 0 for width, 1 for height + * @param {number} defaultVal - fallback value + * @returns {number} + */ +function getVideoSize(playerSize, index, defaultVal) { + if (!playerSize || !Array.isArray(playerSize) || playerSize.length === 0) { + return defaultVal; + } + // Nested: [[640, 480]] or [[640, 480], [320, 240]] + if (Array.isArray(playerSize[0])) { + return playerSize[0][index] || defaultVal; + } + // Flat: [640, 480] + if (typeof playerSize[0] === 'number') { + return playerSize[index] || defaultVal; + } + return defaultVal; +} + +/** + * Detect if adm content is VAST XML (for multi-format video detection). + * @param {string} adm - ad markup string + * @returns {boolean} + */ +function isVastAdm(adm) { + if (typeof adm !== 'string') return false; + const trimmed = adm.trim(); + return trimmed.startsWith(' floor && floorInfo.currency === 'USD' && !isNaN(floorInfo.floor)) { + floor = floorInfo.floor; + } + } catch (e) { + // Continue to next size + } + } + + // Fallback to general floor + if (floor === 0) { + try { + const floorInfo = bid.getFloor({ currency: 'USD', mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(floorInfo.floor)) { + floor = floorInfo.floor; + } + } catch (e) { + logWarn('Revantage: getFloor threw error', e); + } + } + } + return floor; +} + +function getDeviceType() { + if (typeof screen === 'undefined') return 1; + const width = screen.width; + const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; + + if (/iPhone|iPod/i.test(ua) || (width < 768 && /Mobile/i.test(ua))) return 2; // Mobile + if (/iPad/i.test(ua) || (width >= 768 && width < 1024)) return 5; // Tablet + return 1; // Desktop/PC +} + +// === REGISTER === +registerBidder(spec); diff --git a/modules/revantageBidAdapter.md b/modules/revantageBidAdapter.md new file mode 100644 index 00000000000..42a4ef4198d --- /dev/null +++ b/modules/revantageBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: ReVantage Bidder Adapter +Module Type: ReVantage Bidder Adapter +Maintainer: bern@revantage.io +``` + +# Description + +Connects to ReVantage exchange for bids. +ReVantage bid adapter supports Banner and Video. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'revantage', + params: { + feedId: 'testfeed', + } + } + ] + } +``` diff --git a/test/spec/modules/revantageBidAdapter_spec.js b/test/spec/modules/revantageBidAdapter_spec.js new file mode 100644 index 00000000000..b560c218bfd --- /dev/null +++ b/test/spec/modules/revantageBidAdapter_spec.js @@ -0,0 +1,994 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec } from '../../../modules/revantageBidAdapter.js'; +import { newBidder } from '../../../src/adapters/bidderFactory.js'; +import { deepClone } from '../../../src/utils.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from '../../../src/utils.js'; + +const ENDPOINT_URL = 'https://bid.revantage.io/bid'; +const SYNC_URL = 'https://sync.revantage.io/sync'; + +describe('RevantageBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'revantage', + params: { + feedId: 'test-feed-123' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when bid is undefined', function () { + expect(spec.isBidRequestValid(undefined)).to.equal(false); + }); + + it('should return false when bid is null', function () { + expect(spec.isBidRequestValid(null)).to.equal(false); + }); + + it('should return false when params is missing', function () { + const invalidBid = deepClone(validBid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when feedId is missing', function () { + const invalidBid = deepClone(validBid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when feedId is empty string', function () { + const invalidBid = deepClone(validBid); + invalidBid.params = { feedId: '' }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return true with optional params', function () { + const bidWithOptional = deepClone(validBid); + bidWithOptional.params.placementId = 'test-placement'; + bidWithOptional.params.publisherId = 'test-publisher'; + expect(spec.isBidRequestValid(bidWithOptional)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const validBidRequests = [{ + bidder: 'revantage', + params: { + feedId: 'test-feed-123', + placementId: 'test-placement', + publisherId: 'test-publisher' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor: function(params) { + return { + currency: 'USD', + floor: 0.5 + }; + } + }]; + + const bidderRequest = { + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gdprApplies: true + }, + uspConsent: '1---', + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7, 8] + }, + ortb2: { + site: { + domain: 'example.com', + page: 'https://example.com/test' + }, + device: { + ua: 'Mozilla/5.0...', + language: 'en' + } + } + }; + + it('should return valid request object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.include(ENDPOINT_URL); + expect(request.url).to.include('feed=test-feed-123'); + expect(request.options.contentType).to.equal('text/plain'); + expect(request.options.withCredentials).to.equal(false); + expect(request.bidRequests).to.equal(validBidRequests); + }); + + it('should include all required OpenRTB fields', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.id).to.equal('1d1a030790a475'); + expect(data.imp).to.be.an('array').with.lengthOf(1); + expect(data.site).to.be.an('object'); + expect(data.device).to.be.an('object'); + expect(data.user).to.be.an('object'); + expect(data.regs).to.be.an('object'); + expect(data.cur).to.deep.equal(['USD']); + expect(data.tmax).to.equal(3000); + }); + + it('should build correct impression object', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.id).to.equal('30b31c1838de1e'); + expect(imp.tagid).to.equal('adunit-code'); + expect(imp.bidfloor).to.equal(0.5); + expect(imp.banner).to.be.an('object'); + expect(imp.banner.w).to.equal(300); + expect(imp.banner.h).to.equal(250); + expect(imp.banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 300, h: 600 } + ]); + }); + + it('should include bidder-specific ext parameters', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.ext.feedId).to.equal('test-feed-123'); + expect(imp.ext.bidder.placementId).to.equal('test-placement'); + expect(imp.ext.bidder.publisherId).to.equal('test-publisher'); + }); + + it('should include GDPR consent data', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + }); + + it('should include CCPA/USP consent', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.regs.ext.us_privacy).to.equal('1---'); + }); + + it('should include GPP consent with sections as array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.regs.ext.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.ext.gpp_sid).to.deep.equal([7, 8]); + }); + + it('should handle GDPR not applies', function () { + const bidderRequestNoGdpr = deepClone(bidderRequest); + bidderRequestNoGdpr.gdprConsent.gdprApplies = false; + + const request = spec.buildRequests(validBidRequests, bidderRequestNoGdpr); + const data = JSON.parse(request.data); + + expect(data.regs.ext.gdpr).to.equal(0); + }); + + it('should handle missing getFloor function', function () { + const bidRequestsWithoutFloor = deepClone(validBidRequests); + delete bidRequestsWithoutFloor[0].getFloor; + + const request = spec.buildRequests(bidRequestsWithoutFloor, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp[0].bidfloor).to.equal(0); + }); + + it('should handle getFloor returning non-USD currency', function () { + const bidRequestsEurFloor = deepClone(validBidRequests); + bidRequestsEurFloor[0].getFloor = function() { + return { currency: 'EUR', floor: 0.5 }; + }; + + const request = spec.buildRequests(bidRequestsEurFloor, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp[0].bidfloor).to.equal(0); + }); + + it('should handle missing ortb2 data', function () { + const bidderRequestNoOrtb2 = deepClone(bidderRequest); + delete bidderRequestNoOrtb2.ortb2; + + const request = spec.buildRequests(validBidRequests, bidderRequestNoOrtb2); + const data = JSON.parse(request.data); + + expect(data.site).to.be.an('object'); + expect(data.site.domain).to.exist; + expect(data.device).to.be.an('object'); + }); + + it('should include supply chain when present in bidderRequest', function () { + const bidderRequestWithSchain = deepClone(bidderRequest); + bidderRequestWithSchain.schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '12345', + hp: 1 + }] + }; + + const request = spec.buildRequests(validBidRequests, bidderRequestWithSchain); + const data = JSON.parse(request.data); + + expect(data.schain).to.exist; + expect(data.schain.ver).to.equal('1.0'); + expect(data.schain.complete).to.equal(1); + expect(data.schain.nodes).to.have.lengthOf(1); + }); + + it('should include supply chain from first bid request', function () { + const bidRequestsWithSchain = deepClone(validBidRequests); + bidRequestsWithSchain[0].schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'bidder.com', sid: '999', hp: 1 }] + }; + + const bidderRequestNoSchain = deepClone(bidderRequest); + delete bidderRequestNoSchain.schain; + + const request = spec.buildRequests(bidRequestsWithSchain, bidderRequestNoSchain); + const data = JSON.parse(request.data); + + expect(data.schain).to.exist; + expect(data.schain.nodes[0].asi).to.equal('bidder.com'); + }); + + it('should include user EIDs when present', function () { + const bidRequestsWithEids = deepClone(validBidRequests); + bidRequestsWithEids[0].userIdAsEids = [ + { + source: 'id5-sync.com', + uids: [{ id: 'test-id5-id', atype: 1 }] + } + ]; + + const request = spec.buildRequests(bidRequestsWithEids, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.user.eids).to.be.an('array'); + expect(data.user.eids[0].source).to.equal('id5-sync.com'); + }); + + it('should return empty array when feedIds differ across bids', function () { + const mixedFeedBidRequests = [ + { + bidder: 'revantage', + params: { feedId: 'feed-1' }, + adUnitCode: 'adunit-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + sizes: [[300, 250]], + bidId: 'bid1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }, + { + bidder: 'revantage', + params: { feedId: 'feed-2' }, + adUnitCode: 'adunit-2', + mediaTypes: { banner: { sizes: [[728, 90]] } }, + sizes: [[728, 90]], + bidId: 'bid2', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + } + ]; + + const request = spec.buildRequests(mixedFeedBidRequests, bidderRequest); + expect(request).to.deep.equal([]); + }); + + it('should return empty array on exception', function () { + const request = spec.buildRequests(null, bidderRequest); + expect(request).to.deep.equal([]); + }); + + it('should handle video media type', function () { + const videoBidRequests = [{ + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'video-adunit', + bidId: 'video-bid-1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4', 'video/webm'], + protocols: [2, 3, 5, 6], + api: [1, 2], + placement: 1, + minduration: 5, + maxduration: 30, + skip: 1, + skipmin: 5, + skipafter: 5 + } + } + }]; + + const request = spec.buildRequests(videoBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.video).to.exist; + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.mimes).to.deep.equal(['video/mp4', 'video/webm']); + expect(imp.video.protocols).to.deep.equal([2, 3, 5, 6]); + expect(imp.video.minduration).to.equal(5); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.skip).to.equal(1); + expect(imp.banner).to.be.undefined; + }); + + it('should handle multi-format (banner + video) bid', function () { + const multiFormatBidRequests = [{ + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'multi-format-adunit', + bidId: 'multi-bid-1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'] + } + } + }]; + + const request = spec.buildRequests(multiFormatBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const imp = data.imp[0]; + + expect(imp.banner).to.exist; + expect(imp.video).to.exist; + }); + + it('should handle multiple impressions with same feedId', function () { + const multipleBidRequests = [ + { + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'adunit-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + sizes: [[300, 250]], + bidId: 'bid1', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }, + { + bidder: 'revantage', + params: { feedId: 'test-feed-123' }, + adUnitCode: 'adunit-2', + mediaTypes: { banner: { sizes: [[728, 90]] } }, + sizes: [[728, 90]], + bidId: 'bid2', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + } + ]; + + const request = spec.buildRequests(multipleBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp).to.have.lengthOf(2); + expect(data.imp[0].id).to.equal('bid1'); + expect(data.imp[1].id).to.equal('bid2'); + }); + + it('should use default sizes when sizes array is empty', function () { + const bidWithEmptySizes = [{ + bidder: 'revantage', + params: { feedId: 'test-feed' }, + adUnitCode: 'adunit-code', + mediaTypes: { banner: { sizes: [] } }, + sizes: [], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }]; + + const request = spec.buildRequests(bidWithEmptySizes, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + }); + + it('should include prebid version in ext', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.ext).to.exist; + expect(data.ext.prebid).to.exist; + expect(data.ext.prebid.version).to.exist; + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + id: '1d1a030790a475', + seatbid: [{ + seat: 'test-dsp', + bid: [{ + id: 'test-bid-id', + impid: '30b31c1838de1e', + price: 1.25, + crid: 'test-creative-123', + adm: '
Test Ad Markup
', + w: 300, + h: 250, + adomain: ['advertiser.com'], + dealid: 'deal-123' + }] + }], + cur: 'USD' + } + }; + + const bidRequest = { + bidRequests: [{ + bidId: '30b31c1838de1e', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }] + }; + + it('should return valid banner bid response', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + + expect(result).to.be.an('array').with.lengthOf(1); + + const bid = result[0]; + expect(bid.requestId).to.equal('30b31c1838de1e'); + expect(bid.cpm).to.equal(1.25); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('test-creative-123'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.ad).to.equal('
Test Ad Markup
'); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.dealId).to.equal('deal-123'); + }); + + it('should include meta data in bid response', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + const bid = result[0]; + + expect(bid.meta).to.be.an('object'); + expect(bid.meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(bid.meta.dsp).to.equal('test-dsp'); + expect(bid.meta.networkName).to.equal('Revantage'); + }); + + it('should include burl when provided', function () { + const responseWithBurl = deepClone(serverResponse); + responseWithBurl.body.seatbid[0].bid[0].burl = 'https://bid.revantage.io/win?auction=1d1a030790a475&dsp=test-dsp&price=0.625000&impid=30b31c1838de1e&bidid=test-bid-id&adid=test-creative-123&page=&domain=&country=&feedid=test-feed&ref='; + + const result = spec.interpretResponse(responseWithBurl, bidRequest); + const bid = result[0]; + + expect(bid.burl).to.include('https://bid.revantage.io/win'); + expect(bid.burl).to.include('dsp=test-dsp'); + expect(bid.burl).to.include('impid=30b31c1838de1e'); + }); + + it('should handle video response with vastXml', function () { + const videoResponse = deepClone(serverResponse); + videoResponse.body.seatbid[0].bid[0].vastXml = '...'; + delete videoResponse.body.seatbid[0].bid[0].adm; + + const videoBidRequest = { + bidRequests: [{ + bidId: '30b31c1838de1e', + adUnitCode: 'video-adunit', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + } + }] + }; + + const result = spec.interpretResponse(videoResponse, videoBidRequest); + const bid = result[0]; + + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.vastXml).to.equal('...'); + }); + + it('should handle video response with vastUrl', function () { + const videoResponse = deepClone(serverResponse); + videoResponse.body.seatbid[0].bid[0].vastUrl = 'https://vast.example.com/vast.xml'; + delete videoResponse.body.seatbid[0].bid[0].adm; + + const videoBidRequest = { + bidRequests: [{ + bidId: '30b31c1838de1e', + adUnitCode: 'video-adunit', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + } + }] + }; + + const result = spec.interpretResponse(videoResponse, videoBidRequest); + const bid = result[0]; + + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.vastUrl).to.equal('https://vast.example.com/vast.xml'); + }); + + it('should detect video from ext.mediaType', function () { + const videoResponse = deepClone(serverResponse); + videoResponse.body.seatbid[0].bid[0].adm = '...'; + videoResponse.body.seatbid[0].bid[0].ext = { mediaType: 'video' }; + + const result = spec.interpretResponse(videoResponse, bidRequest); + const bid = result[0]; + + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.vastXml).to.equal('...'); + }); + + it('should use default dimensions from bid request when missing in response', function () { + const responseNoDimensions = deepClone(serverResponse); + delete responseNoDimensions.body.seatbid[0].bid[0].w; + delete responseNoDimensions.body.seatbid[0].bid[0].h; + + const result = spec.interpretResponse(responseNoDimensions, bidRequest); + const bid = result[0]; + + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + }); + + it('should include dspPrice from ext when available', function () { + const responseWithDspPrice = deepClone(serverResponse); + responseWithDspPrice.body.seatbid[0].bid[0].ext = { dspPrice: 1.50 }; + + const result = spec.interpretResponse(responseWithDspPrice, bidRequest); + const bid = result[0]; + + expect(bid.meta.dspPrice).to.equal(1.50); + }); + + it('should return empty array for null response body', function () { + const result = spec.interpretResponse({ body: null }, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should return empty array for undefined response body', function () { + const result = spec.interpretResponse({}, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should return empty array when seatbid is not an array', function () { + const invalidResponse = { + body: { + id: '1d1a030790a475', + seatbid: 'not-an-array', + cur: 'USD' + } + }; + + const result = spec.interpretResponse(invalidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should return empty array for empty seatbid', function () { + const emptyResponse = { + body: { + id: '1d1a030790a475', + seatbid: [], + cur: 'USD' + } + }; + + const result = spec.interpretResponse(emptyResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids with zero price', function () { + const zeroPriceResponse = deepClone(serverResponse); + zeroPriceResponse.body.seatbid[0].bid[0].price = 0; + + const result = spec.interpretResponse(zeroPriceResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids with negative price', function () { + const negativePriceResponse = deepClone(serverResponse); + negativePriceResponse.body.seatbid[0].bid[0].price = -1; + + const result = spec.interpretResponse(negativePriceResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids without ad markup', function () { + const noAdmResponse = deepClone(serverResponse); + delete noAdmResponse.body.seatbid[0].bid[0].adm; + + const result = spec.interpretResponse(noAdmResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should filter out bids with unknown impid', function () { + const unknownImpidResponse = deepClone(serverResponse); + unknownImpidResponse.body.seatbid[0].bid[0].impid = 'unknown-imp-id'; + + const result = spec.interpretResponse(unknownImpidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('should handle missing bidRequests in request object', function () { + const result = spec.interpretResponse(serverResponse, {}); + expect(result).to.deep.equal([]); + }); + + it('should handle multiple seatbids', function () { + const multiSeatResponse = deepClone(serverResponse); + multiSeatResponse.body.seatbid.push({ + seat: 'another-dsp', + bid: [{ + id: 'another-bid-id', + impid: 'another-imp-id', + price: 2.00, + crid: 'another-creative', + adm: '
Another Ad
', + w: 728, + h: 90, + adomain: ['another-advertiser.com'] + }] + }); + + const multiBidRequest = { + bidRequests: [ + { + bidId: '30b31c1838de1e', + adUnitCode: 'adunit-code', + mediaTypes: { banner: { sizes: [[300, 250]] } } + }, + { + bidId: 'another-imp-id', + adUnitCode: 'adunit-code-2', + mediaTypes: { banner: { sizes: [[728, 90]] } } + } + ] + }; + + const result = spec.interpretResponse(multiSeatResponse, multiBidRequest); + + expect(result).to.have.lengthOf(2); + expect(result[0].meta.dsp).to.equal('test-dsp'); + expect(result[1].meta.dsp).to.equal('another-dsp'); + }); + + it('should use default currency USD when not specified', function () { + const noCurrencyResponse = deepClone(serverResponse); + delete noCurrencyResponse.body.cur; + + const result = spec.interpretResponse(noCurrencyResponse, bidRequest); + const bid = result[0]; + + expect(bid.currency).to.equal('USD'); + }); + + it('should generate creativeId when crid is missing', function () { + const noCridResponse = deepClone(serverResponse); + delete noCridResponse.body.seatbid[0].bid[0].crid; + + const result = spec.interpretResponse(noCridResponse, bidRequest); + const bid = result[0]; + + expect(bid.creativeId).to.exist; + expect(bid.creativeId).to.satisfy(crid => + crid === 'test-bid-id' || crid.startsWith('revantage-') + ); + }); + + it('should handle empty adomain array', function () { + const noAdomainResponse = deepClone(serverResponse); + delete noAdomainResponse.body.seatbid[0].bid[0].adomain; + + const result = spec.interpretResponse(noAdomainResponse, bidRequest); + const bid = result[0]; + + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); + + it('should use "unknown" for missing seat', function () { + const noSeatResponse = deepClone(serverResponse); + delete noSeatResponse.body.seatbid[0].seat; + + const result = spec.interpretResponse(noSeatResponse, bidRequest); + const bid = result[0]; + + expect(bid.meta.dsp).to.equal('unknown'); + }); + }); + + describe('getUserSyncs', function () { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + + const gdprConsent = { + gdprApplies: true, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + + const uspConsent = '1---'; + + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7, 8] + }; + + it('should return iframe sync when iframe enabled', function () { + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + gdprConsent, + uspConsent, + gppConsent + ); + + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(SYNC_URL); + }); + + it('should return pixel sync when pixel enabled', function () { + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: true }, + [], + gdprConsent, + uspConsent, + gppConsent + ); + + expect(syncs).to.be.an('array').with.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.include(SYNC_URL); + expect(syncs[0].url).to.include('tag=img'); + }); + + it('should return both syncs when both enabled', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs).to.have.lengthOf(2); + expect(syncs.map(s => s.type)).to.include('iframe'); + expect(syncs.map(s => s.type)).to.include('image'); + }); + + it('should include cache buster parameter', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('cb='); + }); + + it('should include GDPR parameters when consent applies', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D'); + }); + + it('should set gdpr=0 when GDPR does not apply', function () { + const gdprNotApplies = { + gdprApplies: false, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprNotApplies, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gdpr=0'); + }); + + it('should include USP consent parameter', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('us_privacy=1---'); + }); + + it('should include GPP parameters', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs[0].url).to.include('gpp='); + expect(syncs[0].url).to.include('gpp_sid=7%2C8'); + }); + + it('should handle missing GDPR consent', function () { + const syncs = spec.getUserSyncs(syncOptions, [], null, uspConsent, gppConsent); + + expect(syncs[0].url).to.not.include('gdpr='); + expect(syncs[0].url).to.not.include('gdpr_consent='); + }); + + it('should handle missing USP consent', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, null, gppConsent); + + expect(syncs[0].url).to.not.include('us_privacy='); + }); + + it('should handle missing GPP consent', function () { + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, null); + + expect(syncs[0].url).to.not.include('gpp='); + expect(syncs[0].url).to.not.include('gpp_sid='); + }); + + it('should handle undefined GPP string', function () { + const partialGppConsent = { + applicableSections: [7, 8] + }; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, partialGppConsent); + + expect(syncs[0].url).to.not.include('gpp='); + expect(syncs[0].url).to.include('gpp_sid=7%2C8'); + }); + + it('should return empty array when no sync options enabled', function () { + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: false }, + [], + gdprConsent, + uspConsent, + gppConsent + ); + + expect(syncs).to.be.an('array').that.is.empty; + }); + + it('should return empty array when syncOptions is empty object', function () { + const syncs = spec.getUserSyncs({}, [], gdprConsent, uspConsent, gppConsent); + + expect(syncs).to.be.an('array').that.is.empty; + }); + }); + + describe('onBidWon', function () { + let triggerPixelStub; + + beforeEach(function () { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + triggerPixelStub.restore(); + }); + + it('should call triggerPixel with correct burl', function () { + const bid = { + bidId: '30b31c1838de1e', + cpm: 1.25, + adUnitCode: 'adunit-code', + burl: 'https://bid.revantage.io/win?auction=1d1a030790a475&dsp=test-dsp&price=0.625000&impid=30b31c1838de1e&bidid=test-bid-id&adid=test-ad-123&page=https%3A%2F%2Fexample.com&domain=example.com&country=US&feedid=test-feed&ref=' + }; + + spec.onBidWon(bid); + + expect(triggerPixelStub.calledOnce).to.be.true; + expect(triggerPixelStub.firstCall.args[0]).to.include('https://bid.revantage.io/win'); + expect(triggerPixelStub.firstCall.args[0]).to.include('dsp=test-dsp'); + expect(triggerPixelStub.firstCall.args[0]).to.include('impid=30b31c1838de1e'); + expect(triggerPixelStub.firstCall.args[0]).to.include('feedid=test-feed'); + }); + + it('should not throw error when burl is missing', function () { + const bid = { + bidId: '30b31c1838de1e', + cpm: 1.25, + adUnitCode: 'adunit-code' + }; + + expect(() => spec.onBidWon(bid)).to.not.throw(); + expect(triggerPixelStub.called).to.be.false; + }); + + it('should handle burl with all query parameters', function () { + // This is the actual format generated by your RTB server + const burl = 'https://bid.revantage.io/win?' + + 'auction=auction_123456789' + + '&dsp=Improve_Digital' + + '&price=0.750000' + + '&impid=imp_001%7Cfeed123' + // URL encoded pipe for feedId in impid + '&bidid=bid_abc' + + '&adid=creative_xyz' + + '&page=https%3A%2F%2Fexample.com%2Fpage' + + '&domain=example.com' + + '&country=US' + + '&feedid=feed123' + + '&ref=https%3A%2F%2Fgoogle.com'; + + const bid = { + bidId: 'imp_001', + cpm: 1.50, + burl: burl + }; + + spec.onBidWon(bid); + + expect(triggerPixelStub.calledOnce).to.be.true; + const calledUrl = triggerPixelStub.firstCall.args[0]; + expect(calledUrl).to.include('auction=auction_123456789'); + expect(calledUrl).to.include('dsp=Improve_Digital'); + expect(calledUrl).to.include('price=0.750000'); + expect(calledUrl).to.include('domain=example.com'); + expect(calledUrl).to.include('country=US'); + expect(calledUrl).to.include('feedid=feed123'); + }); + }); + + describe('spec properties', function () { + it('should have correct bidder code', function () { + expect(spec.code).to.equal('revantage'); + }); + + it('should support banner and video media types', function () { + expect(spec.supportedMediaTypes).to.deep.equal([BANNER, VIDEO]); + }); + }); +}); From 4fb207b34e3cc8b6b0f8ccc0f9c2a156e0623158 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Mon, 23 Feb 2026 18:13:23 +0300 Subject: [PATCH 068/105] AdMatic Bid Adapter : add adrubi alias (#14504) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * admatic sync update * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 11e053f0743f2df0b88bb2010f8c26b08653516a. * Revert "Update admaticBidAdapter.js" This reverts commit 11e053f0743f2df0b88bb2010f8c26b08653516a. * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 4e75f7e583f..d83e7ed5ae9 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -28,6 +28,7 @@ export const spec = { { code: 'monetixads', gvlid: 1281 }, { code: 'netaddiction', gvlid: 1281 }, { code: 'adt', gvlid: 779 }, + { code: 'adrubi', gvlid: 779 }, { code: 'yobee', gvlid: 1281 } ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 371a9de1c56974e433b417221603e7d812b38ba4 Mon Sep 17 00:00:00 2001 From: Siminko Vlad <85431371+siminkovladyslav@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:32:34 +0100 Subject: [PATCH 069/105] OMS Bid Adapter: add instl flag to imp in request (#14501) --- modules/omsBidAdapter.js | 5 +++++ test/spec/modules/omsBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 95498260680..18f7c7aa12a 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -8,6 +8,7 @@ import { getBidIdParameter, getUniqueIdentifierStr, formatQS, + deepAccess, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -73,6 +74,10 @@ function buildRequests(bidReqs, bidderRequest) { } } + if (deepAccess(bid, 'ortb2Imp.instl') === 1) { + imp.instl = 1; + } + const bidFloor = getBidFloor(bid); if (bidFloor) { diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index 3b9c0779fb0..da9c21e7ae3 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -267,6 +267,16 @@ describe('omsBidAdapter', function () { expect(data.regs.coppa).to.equal(1); }); + it('sends instl property when ortb2Imp.instl = 1', function () { + const data = JSON.parse(spec.buildRequests([{ ...bidRequests[0], ortb2Imp: { instl: 1 }}]).data); + expect(data.imp[0].instl).to.equal(1); + }); + + it('ignores instl property when ortb2Imp.instl is falsy', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.imp[0].instl).to.be.undefined; + }); + it('sends schain', function () { const data = JSON.parse(spec.buildRequests(bidRequests).data); expect(data).to.not.be.undefined; From a7b5796a1fba59552ba130c3fe2b7c18d42ce28d Mon Sep 17 00:00:00 2001 From: abermanov-zeta <95416296+abermanov-zeta@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:34:27 +0100 Subject: [PATCH 070/105] Zeta SSP Analytics Adapter: pass floors. (#14350) * Zeta SSP Analytics Adapter: pass floors. * Zeta SSP Analytics Adapter: minor fix. * Zeta SSP Analytics Adapter: fix tests. --- modules/zeta_global_sspAnalyticsAdapter.js | 85 ++++++++++---- .../zeta_global_sspAnalyticsAdapter_spec.js | 104 +++++++++++++----- 2 files changed, 139 insertions(+), 50 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index ed4971d39a7..46e7f9d5951 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -6,6 +6,7 @@ import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import {config} from '../src/config.js'; import {parseDomain} from '../src/refererDetection.js'; +import {BANNER, VIDEO} from "../src/mediaTypes.js"; const ZETA_GVL_ID = 833; const ADAPTER_CODE = 'zeta_global_ssp'; @@ -40,12 +41,14 @@ function adRenderSucceededHandler(args) { auctionId: args.bid?.auctionId, creativeId: args.bid?.creativeId, bidder: args.bid?.bidderCode, + dspId: args.bid?.dspId, mediaType: args.bid?.mediaType, size: args.bid?.size, adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, cpm: args.bid?.cpm, - adUnitCode: args.bid?.adUnitCode + adUnitCode: args.bid?.adUnitCode, + floorData: args.bid?.floorData }, device: { ua: navigator.userAgent @@ -61,15 +64,35 @@ function auctionEndHandler(args) { bidderCode: br?.bidderCode, domain: br?.refererInfo?.domain, page: br?.refererInfo?.page, - bids: br?.bids?.map(b => ({ - bidId: b?.bidId, - auctionId: b?.auctionId, - bidder: b?.bidder, - mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), - size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), - device: b?.ortb2?.device, - adUnitCode: b?.adUnitCode - })) + bids: br?.bids?.map(b => { + const mediaType = b?.mediaTypes?.video ? VIDEO : (b?.mediaTypes?.banner ? BANNER : undefined); + let floor; + if (typeof b?.getFloor === 'function') { + try { + const floorInfo = b.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + if (floorInfo && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (e) { + // ignore floor lookup errors + } + } + + return { + bidId: b?.bidId, + auctionId: b?.auctionId, + bidder: b?.bidder, + mediaType: mediaType, + sizes: b?.sizes, + device: b?.ortb2?.device, + adUnitCode: b?.adUnitCode, + floor: floor + }; + }) })), bidsReceived: args.bidsReceived?.map(br => ({ adId: br?.adId, @@ -81,7 +104,8 @@ function auctionEndHandler(args) { adomain: br?.adserverTargeting?.hb_adomain, timeToRespond: br?.timeToRespond, cpm: br?.cpm, - adUnitCode: br?.adUnitCode + adUnitCode: br?.adUnitCode, + dspId: br?.dspId })) } sendEvent(EVENTS.AUCTION_END, event); @@ -92,16 +116,35 @@ function bidTimeoutHandler(args) { zetaParams: zetaParams, domain: args.find(t => t?.ortb2?.site?.domain)?.ortb2?.site?.domain, page: args.find(t => t?.ortb2?.site?.page)?.ortb2?.site?.page, - timeouts: args.map(t => ({ - bidId: t?.bidId, - auctionId: t?.auctionId, - bidder: t?.bidder, - mediaType: t?.mediaTypes?.video ? 'VIDEO' : (t?.mediaTypes?.banner ? 'BANNER' : undefined), - size: t?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), - timeout: t?.timeout, - device: t?.ortb2?.device, - adUnitCode: t?.adUnitCode - })) + timeouts: args.map(t => { + const mediaType = t?.mediaTypes?.video ? VIDEO : (t?.mediaTypes?.banner ? BANNER : undefined); + let floor; + if (typeof t?.getFloor === 'function') { + try { + const floorInfo = t.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + if (floorInfo && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (e) { + // ignore floor lookup errors + } + } + return { + bidId: t?.bidId, + auctionId: t?.auctionId, + bidder: t?.bidder, + mediaType: mediaType, + sizes: t?.sizes, + timeout: t?.timeout, + device: t?.ortb2?.device, + adUnitCode: t?.adUnitCode, + floor: floor + } + }) } sendEvent(EVENTS.BID_TIMEOUT, event); } diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 6e55083338f..d3e6e01c655 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -1,8 +1,7 @@ import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; -import {config} from 'src/config'; -import {EVENTS} from 'src/constants.js'; -import {server} from '../../mocks/xhr.js'; -import {logError} from '../../../src/utils.js'; +import { config } from 'src/config'; +import { EVENTS } from 'src/constants.js'; +import { server } from '../../mocks/xhr.js'; const utils = require('src/utils'); const events = require('src/events'); @@ -112,6 +111,9 @@ const SAMPLE_EVENTS = { 'device': { 'mobile': 1 } + }, + 'getFloor': function() { + return { floor: 1.5, currency: 'USD' }; } } ], @@ -178,6 +180,9 @@ const SAMPLE_EVENTS = { 'device': { 'mobile': 1 } + }, + 'getFloor': function() { + return { floor: 1.5, currency: 'USD' }; } } ], @@ -275,6 +280,7 @@ const SAMPLE_EVENTS = { 'pbDg': '2.25', 'pbCg': '', 'size': '480x320', + 'dspId': 'test-dsp-id-123', 'adserverTargeting': { 'hb_bidder': 'zeta_global_ssp', 'hb_adid': '5759bb3ef7be1e8', @@ -337,6 +343,7 @@ const SAMPLE_EVENTS = { 'adUnitCode': '/19968336/header-bid-tag-0', 'timeToRespond': 123, 'size': '480x320', + 'dspId': 'test-dsp-id-123', 'adserverTargeting': { 'hb_bidder': 'zeta_global_ssp', 'hb_adid': '5759bb3ef7be1e8', @@ -351,7 +358,12 @@ const SAMPLE_EVENTS = { { 'nonZetaParam': 'nonZetaValue' } - ] + ], + 'floorData': { + 'floorValue': 1.5, + 'floorCurrency': 'USD', + 'floorRule': 'test-rule' + } }, 'adId': '5759bb3ef7be1e8' }, @@ -435,7 +447,10 @@ const SAMPLE_EVENTS = { } } }, - 'timeout': 3 + 'timeout': 3, + 'getFloor': function() { + return { floor: 0.75, currency: 'USD' }; + } }, { 'bidder': 'zeta_global_ssp', @@ -516,7 +531,10 @@ const SAMPLE_EVENTS = { } } }, - 'timeout': 3 + 'timeout': 3, + 'getFloor': function() { + return { floor: 0.75, currency: 'USD' }; + } } ] } @@ -562,14 +580,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { zetaAnalyticsAdapter.disableAnalytics(); }); - it('Handle events', function () { - this.timeout(3000); - + it('should handle AUCTION_END event', function () { events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); - events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); - events.emit(EVENTS.BID_TIMEOUT, SAMPLE_EVENTS.BID_TIMEOUT); - expect(requests.length).to.equal(3); + expect(requests.length).to.equal(1); const auctionEnd = JSON.parse(requests[0].requestBody); expect(auctionEnd).to.be.deep.equal({ zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, @@ -582,11 +596,15 @@ describe('Zeta Global SSP Analytics Adapter', function () { bidId: '206be9a13236af', auctionId: '75e394d9', bidder: 'zeta_global_ssp', - mediaType: 'BANNER', - size: '300x250', + mediaType: 'banner', + sizes: [ + [300, 250], + [300, 600] + ], device: { mobile: 1 - } + }, + floor: 1.5 }] }, { bidderCode: 'appnexus', @@ -597,11 +615,15 @@ describe('Zeta Global SSP Analytics Adapter', function () { bidId: '41badc0e164c758', auctionId: '75e394d9', bidder: 'appnexus', - mediaType: 'BANNER', - size: '300x250', + mediaType: 'banner', + sizes: [ + [300, 250], + [300, 600] + ], device: { mobile: 1 - } + }, + floor: 1.5 }] }], bidsReceived: [{ @@ -614,10 +636,17 @@ describe('Zeta Global SSP Analytics Adapter', function () { size: '480x320', adomain: 'example.adomain', timeToRespond: 123, - cpm: 2.258302852806723 + cpm: 2.258302852806723, + dspId: 'test-dsp-id-123' }] }); - const auctionSucceeded = JSON.parse(requests[1].requestBody); + }); + + it('should handle AD_RENDER_SUCCEEDED event', function () { + events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); + + expect(requests.length).to.equal(1); + const auctionSucceeded = JSON.parse(requests[0].requestBody); expect(auctionSucceeded.zetaParams).to.be.deep.equal({ sid: 111, tags: { @@ -634,15 +663,26 @@ describe('Zeta Global SSP Analytics Adapter', function () { auctionId: '75e394d9', creativeId: '456456456', bidder: 'zeta_global_ssp', + dspId: 'test-dsp-id-123', mediaType: 'banner', size: '480x320', adomain: 'example.adomain', timeToRespond: 123, - cpm: 2.258302852806723 + cpm: 2.258302852806723, + floorData: { + floorValue: 1.5, + floorCurrency: 'USD', + floorRule: 'test-rule' + } }); expect(auctionSucceeded.device.ua).to.not.be.empty; + }); + + it('should handle BID_TIMEOUT event', function () { + events.emit(EVENTS.BID_TIMEOUT, SAMPLE_EVENTS.BID_TIMEOUT); - const bidTimeout = JSON.parse(requests[2].requestBody); + expect(requests.length).to.equal(1); + const bidTimeout = JSON.parse(requests[0].requestBody); expect(bidTimeout.zetaParams).to.be.deep.equal({ sid: 111, tags: { @@ -656,8 +696,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { 'bidId': '27c8c05823e2f', 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', 'bidder': 'zeta_global_ssp', - 'mediaType': 'BANNER', - 'size': '300x250', + 'mediaType': 'banner', + 'sizes': [ + [300, 250] + ], 'timeout': 3, 'device': { 'w': 807, @@ -675,13 +717,16 @@ describe('Zeta Global SSP Analytics Adapter', function () { 'mobile': 0 } }, - 'adUnitCode': 'ad-1' + 'adUnitCode': 'ad-1', + 'floor': 0.75 }, { 'bidId': '31a3b551cbf1ed', 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', 'bidder': 'zeta_global_ssp', - 'mediaType': 'BANNER', - 'size': '300x250', + 'mediaType': 'banner', + 'sizes': [ + [300, 250] + ], 'timeout': 3, 'device': { 'w': 807, @@ -699,7 +744,8 @@ describe('Zeta Global SSP Analytics Adapter', function () { 'mobile': 0 } }, - 'adUnitCode': 'ad-2' + 'adUnitCode': 'ad-2', + 'floor': 0.75 }]); }); }); From b69b8faf1ad2160f79065ba980e43a127084012c Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 23 Feb 2026 08:08:27 -0800 Subject: [PATCH 071/105] Revert "Various modules: remove legacy GPT targeting fallbacks (#14450)" (#14510) This reverts commit 299f20742da2c832cf3f0eaeceb9ee60a043c5a1. --- modules/imRtdProvider.js | 2 +- modules/intentIqIdSystem.js | 4 +- modules/medianetAnalyticsAdapter.js | 7 +-- modules/pubxaiAnalyticsAdapter.js | 7 ++- modules/reconciliationRtdProvider.js | 7 ++- modules/relaidoBidAdapter.js | 10 ++-- modules/relevadRtdProvider.js | 2 +- modules/sirdataRtdProvider.js | 2 +- src/secureCreatives.js | 8 +-- src/targeting.ts | 3 +- src/targeting/lock.ts | 3 +- test/spec/integration/faker/googletag.js | 23 -------- test/spec/modules/intentIqIdSystem_spec.js | 17 ++++-- .../modules/reconciliationRtdProvider_spec.js | 52 ++++--------------- test/spec/unit/core/targetingLock_spec.js | 4 +- test/spec/unit/pbjs_api_spec.js | 36 ------------- test/spec/unit/secureCreatives_spec.js | 1 - 17 files changed, 46 insertions(+), 142 deletions(-) diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index 6577f6311be..46573a81c15 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -115,7 +115,7 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { window.googletag = window.googletag || {cmd: []}; window.googletag.cmd = window.googletag.cmd || []; window.googletag.cmd.push(() => { - window.googletag.setConfig({targeting: {'im_segments': segments}}); + window.googletag.pubads().setTargeting('im_segments', segments); }); } } diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index f684a46b17e..054afe82371 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -208,7 +208,9 @@ export function setGamReporting(gamObjectReference, gamParameterName, userGroup, if (isBlacklisted) return; if (isPlainObject(gamObjectReference) && gamObjectReference.cmd) { gamObjectReference.cmd.push(() => { - gamObjectReference.setConfig({targeting: {[gamParameterName]: userGroup}}); + gamObjectReference + .pubads() + .setTargeting(gamParameterName, userGroup); }); } } diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 969a988b053..734e2120b7b 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -556,12 +556,9 @@ function setupSlotResponseReceivedListener() { mnetGlobals.infoByAdIdMap[adId].srrEvt = slotInf; }; - const targeting = slot.getConfig('targeting'); - const targetingKeys = Object.keys(targeting); - - targetingKeys + slot.getTargetingKeys() .filter((key) => key.startsWith(TARGETING_KEYS.AD_ID)) - .forEach((key) => setSlotResponseInf(targeting[key][0])); + .forEach((key) => setSlotResponseInf(slot.getTargeting(key)[0])); }); }); } diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index d4587476d0c..b2f9af247f6 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -98,16 +98,15 @@ export const auctionCache = new Proxy( const getAdServerDataForBid = (bid) => { const gptSlot = getGptSlotForAdUnitCode(bid); if (gptSlot) { - const targeting = gptSlot.getConfig('targeting'); - const targetingKeys = Object.keys(targeting); return Object.fromEntries( - targetingKeys + gptSlot + .getTargetingKeys() .filter( (key) => key.startsWith('pubx-') || (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) ) - .map((key) => [key, targeting[key]]) + .map((key) => [key, gptSlot.getTargeting(key)]) ); } return {}; // TODO: support more ad servers diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js index b4c75fece57..46486923c0a 100644 --- a/modules/reconciliationRtdProvider.js +++ b/modules/reconciliationRtdProvider.js @@ -64,10 +64,9 @@ function handleAdMessage(e) { // 3. Get AdUnit IDs for the selected slot if (adSlot) { adUnitId = adSlot.getAdUnitPath(); - const targetingConfig = adSlot.getConfig('targeting') || {}; - const rsdkAdId = targetingConfig.RSDK_ADID; - adDeliveryId = Array.isArray(rsdkAdId) && rsdkAdId.length - ? rsdkAdId[0] + adDeliveryId = adSlot.getTargeting('RSDK_ADID'); + adDeliveryId = adDeliveryId.length + ? adDeliveryId[0] : `${timestamp()}-${generateUUID()}`; } } diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index 1a1105992a4..1ef3be58798 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -369,19 +369,17 @@ function getTargeting(bidRequest) { const targetings = {}; const pubads = getPubads(); if (pubads) { - const pubadsTargeting = window.googletag.getConfig('targeting'); - const keys = Object.keys(pubadsTargeting); + const keys = pubads.getTargetingKeys(); for (const key of keys) { - const values = pubadsTargeting[key]; + const values = pubads.getTargeting(key); targetings[key] = values; } } const adUnitSlot = getAdUnit(bidRequest.adUnitCode); if (adUnitSlot) { - const slotTargeting = adUnitSlot.getConfig('targeting'); - const keys = Object.keys(slotTargeting); + const keys = adUnitSlot.getTargetingKeys(); for (const key of keys) { - const values = slotTargeting[key]; + const values = adUnitSlot.getTargeting(key); targetings[key] = values; } } diff --git a/modules/relevadRtdProvider.js b/modules/relevadRtdProvider.js index e10a7afd368..41b2ee797e5 100644 --- a/modules/relevadRtdProvider.js +++ b/modules/relevadRtdProvider.js @@ -231,7 +231,7 @@ export function addRtdData(reqBids, data, moduleConfig) { if (window.googletag && window.googletag.pubads && (typeof window.googletag.pubads === 'function')) { window.googletag.pubads().getSlots().forEach(function (n) { if (typeof n.setTargeting !== 'undefined' && relevadList && relevadList.length > 0) { - n.setConfig({targeting: {'relevad_rtd': relevadList}}); + n.setTargeting('relevad_rtd', relevadList); } }); } diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 81c75692fde..77c6f939a95 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -674,7 +674,7 @@ export function addSegmentData(reqBids, data, adUnits, onDone) { window.googletag.cmd.push(() => { window.googletag.pubads().getSlots().forEach(slot => { if (typeof slot.setTargeting !== 'undefined' && sirdataMergedList.length > 0) { - slot.setConfig({targeting: {'sd_rtd': sirdataMergedList}}); + slot.setTargeting('sd_rtd', sirdataMergedList); } }); }); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index da53770dc2e..8233c373d1a 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -212,12 +212,8 @@ export function resizeRemoteCreative({instl, adId, adUnitCode, width, height}) { 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 fa1f2035799..477ddaab7f0 100644 --- a/src/targeting.ts +++ b/src/targeting.ts @@ -324,8 +324,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/src/targeting/lock.ts b/src/targeting/lock.ts index c3c26d12607..f59d7f1d890 100644 --- a/src/targeting/lock.ts +++ b/src/targeting/lock.ts @@ -45,8 +45,7 @@ export function targetingLock() { const [setupGpt, tearDownGpt] = (() => { let enabled = false; function onGptRender({slot}: SlotRenderEndedEvent) { - const targeting = (slot as any).getConfig('targeting'); - keys?.forEach(key => targeting[key]?.forEach(locked.delete)); + keys?.forEach(key => slot.getTargeting(key)?.forEach(locked.delete)); } return [ () => { diff --git a/test/spec/integration/faker/googletag.js b/test/spec/integration/faker/googletag.js index 4060040b902..a8676b500a5 100644 --- a/test/spec/integration/faker/googletag.js +++ b/test/spec/integration/faker/googletag.js @@ -29,18 +29,6 @@ var Slot = function Slot({ code, divId }) { return []; }, - getConfig: function getConfig(key) { - if (key === 'targeting') { - return this.targeting; - } - }, - - setConfig: function setConfig(config) { - if (config?.targeting) { - this.targeting = config.targeting; - } - }, - clearTargeting: function clearTargeting() { return window.googletag.pubads().getSlots(); } @@ -64,18 +52,7 @@ export function enable() { _slots: [], _callbackMap: {}, _ppid: undefined, - _targeting: {}, cmd: [], - getConfig: function (key) { - if (key === 'targeting') { - return this._targeting; - } - }, - setConfig: function (config) { - if (config?.targeting) { - Object.assign(this._targeting, config.targeting); - } - }, pubads: function () { var self = this; return { diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 5a991b80474..18dd0452943 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -109,9 +109,6 @@ const mockGAM = () => { const targetingObject = {}; return { cmd: [], - setConfig: ({targeting}) => { - Object.assign(targetingObject, targeting); - }, pubads: () => ({ setTargeting: (key, value) => { targetingObject[key] = value; @@ -363,7 +360,17 @@ describe('IntentIQ tests', function () { const expectedGamParameterName = 'intent_iq_group'; defaultConfigParams.params.abPercentage = 0; // "B" provided percentage by user - const setConfigSpy = sinon.spy(mockGamObject, 'setConfig'); + const originalPubads = mockGamObject.pubads; + const setTargetingSpy = sinon.spy(); + mockGamObject.pubads = function () { + const obj = { ...originalPubads.apply(this, arguments) }; + const originalSetTargeting = obj.setTargeting; + obj.setTargeting = function (...args) { + setTargetingSpy(...args); + return originalSetTargeting.apply(this, args); + }; + return obj; + }; defaultConfigParams.params.gamObjectReference = mockGamObject; @@ -387,7 +394,7 @@ describe('IntentIQ tests', function () { expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39'); expect(groupBeforeResponse).to.deep.equal([WITHOUT_IIQ]); expect(groupAfterResponse).to.deep.equal([WITH_IIQ]); - expect(setConfigSpy.calledTwice).to.be.true; + expect(setTargetingSpy.calledTwice).to.be.true; }); it('should set GAM targeting to B when server tc=41', async () => { diff --git a/test/spec/modules/reconciliationRtdProvider_spec.js b/test/spec/modules/reconciliationRtdProvider_spec.js index c36af417564..6efe55ddf46 100644 --- a/test/spec/modules/reconciliationRtdProvider_spec.js +++ b/test/spec/modules/reconciliationRtdProvider_spec.js @@ -163,13 +163,17 @@ describe('Reconciliation Real time data submodule', function () { document.body.appendChild(adSlotElement); const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); - adSlot.setConfig({ - targeting: { - RSDK_AUID: ['/reconciliationAdunit'], - RSDK_ADID: ['12345'] - } - }); + // Fix targeting methods + adSlot.targeting = {}; + adSlot.setTargeting = function(key, value) { + this.targeting[key] = [value]; + }; + adSlot.getTargeting = function(key) { + return this.targeting[key]; + }; + adSlot.setTargeting('RSDK_AUID', '/reconciliationAdunit'); + adSlot.setTargeting('RSDK_ADID', '12345'); adSlotIframe.contentDocument.open(); adSlotIframe.contentDocument.write(``); - adSlotIframe.contentDocument.close(); - - setTimeout(() => { - expect(trackPostStub.calledOnce).to.be.true; - expect(trackPostStub.getCalls()[0].args[1].adUnitId).to.eql('/reconciliationAdunit'); - expect(trackPostStub.getCalls()[0].args[1].adDeliveryId).to.match(/.+-.+/); - done(); - }, 100); - }); }); }); }); diff --git a/test/spec/unit/core/targetingLock_spec.js b/test/spec/unit/core/targetingLock_spec.js index 89ab7972845..b8e721259ca 100644 --- a/test/spec/unit/core/targetingLock_spec.js +++ b/test/spec/unit/core/targetingLock_spec.js @@ -98,9 +98,7 @@ describe('Targeting lock', () => { lock.lock(targeting); eventHandlers.slotRenderEnded({ slot: { - getConfig: sinon.stub().withArgs('targeting').returns({ - k1: [targeting.k1] - }) + getTargeting: (key) => [targeting[key]] } }); expect(lock.isLocked(targeting)).to.be.false; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 6e9955081b4..5939298765e 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -82,24 +82,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; @@ -136,24 +118,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 509fb25140e..084341358b4 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -552,7 +552,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 a5197e75d8f40212f3407e57ba81d039d0a687f1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 23 Feb 2026 16:54:41 +0000 Subject: [PATCH 072/105] Prebid 10.26.0 release --- .../codeql/queries/autogen_fpDOMMethod.qll | 4 +- .../queries/autogen_fpEventProperty.qll | 16 ++-- .../queries/autogen_fpGlobalConstructor.qll | 10 +- .../autogen_fpGlobalObjectProperty0.qll | 54 +++++------ .../autogen_fpGlobalObjectProperty1.qll | 2 +- .../queries/autogen_fpGlobalTypeProperty0.qll | 6 +- .../queries/autogen_fpGlobalTypeProperty1.qll | 2 +- .../codeql/queries/autogen_fpGlobalVar.qll | 18 ++-- .../autogen_fpRenderingContextProperty.qll | 30 +++--- .../queries/autogen_fpSensorProperty.qll | 2 +- metadata/modules.json | 56 +++++++++++ metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.json | 2 +- metadata/modules/acuityadsBidAdapter.json | 2 +- metadata/modules/adagioBidAdapter.json | 2 +- metadata/modules/adagioRtdProvider.json | 2 +- metadata/modules/adbroBidAdapter.json | 2 +- metadata/modules/adclusterBidAdapter.json | 13 +++ 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 | 11 ++- 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 | 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/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/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 | 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 +- .../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/floxisBidAdapter.json | 13 +++ metadata/modules/fwsspBidAdapter.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 | 18 ++++ 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 | 45 +++++++++ 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/leagueMBidAdapter.json | 13 +++ .../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 | 95 ++++++++++++++++++- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.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 | 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 | 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 +- .../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/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/revantageBidAdapter.json | 13 +++ 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/setupadBidAdapter.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/verbenBidAdapter.json | 13 +++ 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 | 33 ++++--- package.json | 2 +- 292 files changed, 667 insertions(+), 382 deletions(-) create mode 100644 metadata/modules/adclusterBidAdapter.json create mode 100644 metadata/modules/floxisBidAdapter.json create mode 100644 metadata/modules/harionBidAdapter.json create mode 100644 metadata/modules/insuradsBidAdapter.json create mode 100644 metadata/modules/leagueMBidAdapter.json create mode 100644 metadata/modules/revantageBidAdapter.json create mode 100644 metadata/modules/verbenBidAdapter.json diff --git a/.github/codeql/queries/autogen_fpDOMMethod.qll b/.github/codeql/queries/autogen_fpDOMMethod.qll index 61555d5852f..164a699e97b 100644 --- a/.github/codeql/queries/autogen_fpDOMMethod.qll +++ b/.github/codeql/queries/autogen_fpDOMMethod.qll @@ -7,9 +7,9 @@ class DOMMethod extends string { DOMMethod() { - ( this = "toDataURL" and weight = 32.78 and type = "HTMLCanvasElement" ) + ( this = "toDataURL" and weight = 32.64 and type = "HTMLCanvasElement" ) or - ( this = "getChannelData" and weight = 1033.52 and type = "AudioBuffer" ) + ( this = "getChannelData" and weight = 1009.41 and type = "AudioBuffer" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpEventProperty.qll b/.github/codeql/queries/autogen_fpEventProperty.qll index a102dc216aa..25ecd018f0f 100644 --- a/.github/codeql/queries/autogen_fpEventProperty.qll +++ b/.github/codeql/queries/autogen_fpEventProperty.qll @@ -7,21 +7,21 @@ class EventProperty extends string { EventProperty() { - ( this = "accelerationIncludingGravity" and weight = 195.95 and event = "devicemotion" ) + ( this = "candidate" and weight = 54.73 and event = "icecandidate" ) or - ( this = "beta" and weight = 889.02 and event = "deviceorientation" ) + ( this = "rotationRate" and weight = 63.55 and event = "devicemotion" ) or - ( this = "gamma" and weight = 318.9 and event = "deviceorientation" ) + ( this = "accelerationIncludingGravity" and weight = 205.08 and event = "devicemotion" ) or - ( this = "alpha" and weight = 748.66 and event = "deviceorientation" ) + ( this = "acceleration" and weight = 64.53 and event = "devicemotion" ) or - ( this = "candidate" and weight = 48.4 and event = "icecandidate" ) + ( this = "alpha" and weight = 784.67 and event = "deviceorientation" ) or - ( this = "acceleration" and weight = 59.13 and event = "devicemotion" ) + ( this = "beta" and weight = 801.42 and event = "deviceorientation" ) or - ( this = "rotationRate" and weight = 58.73 and event = "devicemotion" ) + ( this = "gamma" and weight = 300.01 and event = "deviceorientation" ) or - ( this = "absolute" and weight = 480.46 and event = "deviceorientation" ) + ( this = "absolute" and weight = 281.45 and event = "deviceorientation" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalConstructor.qll b/.github/codeql/queries/autogen_fpGlobalConstructor.qll index 1bd3776448a..8feceaae940 100644 --- a/.github/codeql/queries/autogen_fpGlobalConstructor.qll +++ b/.github/codeql/queries/autogen_fpGlobalConstructor.qll @@ -6,15 +6,15 @@ class GlobalConstructor extends string { GlobalConstructor() { - ( this = "OfflineAudioContext" and weight = 1249.69 ) + ( this = "SharedWorker" and weight = 74.12 ) or - ( this = "SharedWorker" and weight = 78.96 ) + ( this = "OfflineAudioContext" and weight = 1062.83 ) or - ( this = "RTCPeerConnection" and weight = 36.22 ) + ( this = "RTCPeerConnection" and weight = 36.17 ) or - ( this = "Gyroscope" and weight = 94.31 ) + ( this = "Gyroscope" and weight = 100.27 ) or - ( this = "AudioWorkletNode" and weight = 106.77 ) + ( this = "AudioWorkletNode" and weight = 145.12 ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll b/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll index 622b4097377..19489a50149 100644 --- a/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll +++ b/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll @@ -7,59 +7,57 @@ class GlobalObjectProperty0 extends string { GlobalObjectProperty0() { - ( this = "availWidth" and weight = 62.91 and global0 = "screen" ) + ( this = "availHeight" and weight = 65.33 and global0 = "screen" ) or - ( this = "availHeight" and weight = 66.51 and global0 = "screen" ) + ( this = "availWidth" and weight = 61.95 and global0 = "screen" ) or - ( this = "colorDepth" and weight = 36.87 and global0 = "screen" ) + ( this = "colorDepth" and weight = 38.5 and global0 = "screen" ) or - ( this = "pixelDepth" and weight = 43.1 and global0 = "screen" ) + ( this = "availTop" and weight = 1305.37 and global0 = "screen" ) or - ( this = "availLeft" and weight = 730.43 and global0 = "screen" ) + ( this = "plugins" and weight = 15.16 and global0 = "navigator" ) or - ( this = "availTop" and weight = 1485.89 and global0 = "screen" ) + ( this = "deviceMemory" and weight = 64.15 and global0 = "navigator" ) or - ( this = "orientation" and weight = 33.81 and global0 = "screen" ) + ( this = "getBattery" and weight = 41.16 and global0 = "navigator" ) or - ( this = "vendorSub" and weight = 1822.98 and global0 = "navigator" ) + ( this = "webdriver" and weight = 27.64 and global0 = "navigator" ) or - ( this = "productSub" and weight = 381.55 and global0 = "navigator" ) + ( this = "permission" and weight = 24.67 and global0 = "Notification" ) or - ( this = "plugins" and weight = 15.37 and global0 = "navigator" ) + ( this = "storage" and weight = 35.77 and global0 = "navigator" ) or - ( this = "mimeTypes" and weight = 15.39 and global0 = "navigator" ) + ( this = "onLine" and weight = 18.84 and global0 = "navigator" ) or - ( this = "webkitTemporaryStorage" and weight = 32.87 and global0 = "navigator" ) + ( this = "pixelDepth" and weight = 45.77 and global0 = "screen" ) or - ( this = "hardwareConcurrency" and weight = 55.54 and global0 = "navigator" ) + ( this = "availLeft" and weight = 624.44 and global0 = "screen" ) or - ( this = "appCodeName" and weight = 167.7 and global0 = "navigator" ) + ( this = "orientation" and weight = 34.16 and global0 = "screen" ) or - ( this = "onLine" and weight = 18.14 and global0 = "navigator" ) + ( this = "vendorSub" and weight = 1873.27 and global0 = "navigator" ) or - ( this = "webdriver" and weight = 28.99 and global0 = "navigator" ) + ( this = "productSub" and weight = 381.87 and global0 = "navigator" ) or - ( this = "keyboard" and weight = 5673.26 and global0 = "navigator" ) + ( this = "webkitTemporaryStorage" and weight = 37.97 and global0 = "navigator" ) or - ( this = "mediaDevices" and weight = 123.32 and global0 = "navigator" ) + ( this = "hardwareConcurrency" and weight = 51.78 and global0 = "navigator" ) or - ( this = "storage" and weight = 30.23 and global0 = "navigator" ) + ( this = "appCodeName" and weight = 173.35 and global0 = "navigator" ) or - ( this = "deviceMemory" and weight = 62.29 and global0 = "navigator" ) + ( this = "keyboard" and weight = 1722.82 and global0 = "navigator" ) or - ( this = "mediaCapabilities" and weight = 148.31 and global0 = "navigator" ) + ( this = "mediaDevices" and weight = 149.07 and global0 = "navigator" ) or - ( this = "permissions" and weight = 92.01 and global0 = "navigator" ) + ( this = "mediaCapabilities" and weight = 142.34 and global0 = "navigator" ) or - ( this = "permission" and weight = 25.87 and global0 = "Notification" ) + ( this = "permissions" and weight = 89.71 and global0 = "navigator" ) or - ( this = "getBattery" and weight = 40.45 and global0 = "navigator" ) + ( this = "webkitPersistentStorage" and weight = 134.12 and global0 = "navigator" ) or - ( this = "webkitPersistentStorage" and weight = 121.43 and global0 = "navigator" ) + ( this = "requestMediaKeySystemAccess" and weight = 18.22 and global0 = "navigator" ) or - ( this = "requestMediaKeySystemAccess" and weight = 22.53 and global0 = "navigator" ) - or - ( this = "getGamepads" and weight = 275.28 and global0 = "navigator" ) + ( this = "getGamepads" and weight = 209.55 and global0 = "navigator" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll b/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll index 3be175f2c11..4ba664c998f 100644 --- a/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll +++ b/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll @@ -8,7 +8,7 @@ class GlobalObjectProperty1 extends string { GlobalObjectProperty1() { - ( this = "enumerateDevices" and weight = 361.7 and global0 = "navigator" and global1 = "mediaDevices" ) + ( this = "enumerateDevices" and weight = 595.56 and global0 = "navigator" and global1 = "mediaDevices" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll b/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll index 489d3f0f3ae..b26e3689251 100644 --- a/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll +++ b/.github/codeql/queries/autogen_fpGlobalTypeProperty0.qll @@ -7,11 +7,11 @@ class GlobalTypeProperty0 extends string { GlobalTypeProperty0() { - ( this = "x" and weight = 5673.26 and global0 = "Gyroscope" ) + ( this = "x" and weight = 4255.55 and global0 = "Gyroscope" ) or - ( this = "y" and weight = 5673.26 and global0 = "Gyroscope" ) + ( this = "y" and weight = 4255.55 and global0 = "Gyroscope" ) or - ( this = "z" and weight = 5673.26 and global0 = "Gyroscope" ) + ( this = "z" and weight = 4255.55 and global0 = "Gyroscope" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll b/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll index 2f290d30132..084e91305b6 100644 --- a/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll +++ b/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll @@ -8,7 +8,7 @@ class GlobalTypeProperty1 extends string { GlobalTypeProperty1() { - ( this = "resolvedOptions" and weight = 18.94 and global0 = "Intl" and global1 = "DateTimeFormat" ) + ( this = "resolvedOptions" and weight = 19.01 and global0 = "Intl" and global1 = "DateTimeFormat" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalVar.qll b/.github/codeql/queries/autogen_fpGlobalVar.qll index debc39522ee..a28f1c7772c 100644 --- a/.github/codeql/queries/autogen_fpGlobalVar.qll +++ b/.github/codeql/queries/autogen_fpGlobalVar.qll @@ -6,23 +6,23 @@ class GlobalVar extends string { GlobalVar() { - ( this = "devicePixelRatio" and weight = 18.84 ) + ( this = "devicePixelRatio" and weight = 18.39 ) or - ( this = "outerWidth" and weight = 104.3 ) + ( this = "screenX" and weight = 366.36 ) or - ( this = "outerHeight" and weight = 177.3 ) + ( this = "screenY" and weight = 320.66 ) or - ( this = "indexedDB" and weight = 21.68 ) + ( this = "outerWidth" and weight = 104.67 ) or - ( this = "screenX" and weight = 411.93 ) + ( this = "outerHeight" and weight = 154.1 ) or - ( this = "screenY" and weight = 369.99 ) + ( this = "screenLeft" and weight = 321.49 ) or - ( this = "screenLeft" and weight = 344.06 ) + ( this = "screenTop" and weight = 322.32 ) or - ( this = "screenTop" and weight = 343.13 ) + ( this = "indexedDB" and weight = 23.36 ) or - ( this = "openDatabase" and weight = 128.91 ) + ( this = "openDatabase" and weight = 146.11 ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpRenderingContextProperty.qll b/.github/codeql/queries/autogen_fpRenderingContextProperty.qll index 1f23b1a5057..e508d42520b 100644 --- a/.github/codeql/queries/autogen_fpRenderingContextProperty.qll +++ b/.github/codeql/queries/autogen_fpRenderingContextProperty.qll @@ -7,35 +7,35 @@ class RenderingContextProperty extends string { RenderingContextProperty() { - ( this = "getImageData" and weight = 55.51 and contextType = "2d" ) + ( this = "getExtension" and weight = 24.59 and contextType = "webgl" ) or - ( this = "getParameter" and weight = 30.58 and contextType = "webgl" ) + ( this = "getParameter" and weight = 28.11 and contextType = "webgl" ) or - ( this = "measureText" and weight = 46.82 and contextType = "2d" ) + ( this = "getImageData" and weight = 62.25 and contextType = "2d" ) or - ( this = "getParameter" and weight = 70.22 and contextType = "webgl2" ) + ( this = "measureText" and weight = 43.06 and contextType = "2d" ) or - ( this = "getShaderPrecisionFormat" and weight = 128.74 and contextType = "webgl2" ) + ( this = "getParameter" and weight = 67.61 and contextType = "webgl2" ) or - ( this = "getExtension" and weight = 71.78 and contextType = "webgl2" ) + ( this = "getShaderPrecisionFormat" and weight = 138.74 and contextType = "webgl2" ) or - ( this = "getContextAttributes" and weight = 190.28 and contextType = "webgl2" ) + ( this = "getExtension" and weight = 69.66 and contextType = "webgl2" ) or - ( this = "getSupportedExtensions" and weight = 560.85 and contextType = "webgl2" ) + ( this = "getContextAttributes" and weight = 201.04 and contextType = "webgl2" ) or - ( this = "getExtension" and weight = 26.27 and contextType = "webgl" ) + ( this = "getSupportedExtensions" and weight = 360.36 and contextType = "webgl2" ) or - ( this = "getShaderPrecisionFormat" and weight = 1175.17 and contextType = "webgl" ) + ( this = "readPixels" and weight = 24.33 and contextType = "webgl" ) or - ( this = "getContextAttributes" and weight = 1998.53 and contextType = "webgl" ) + ( this = "getShaderPrecisionFormat" and weight = 1347.35 and contextType = "webgl" ) or - ( this = "getSupportedExtensions" and weight = 1388.64 and contextType = "webgl" ) + ( this = "getContextAttributes" and weight = 2411.38 and contextType = "webgl" ) or - ( this = "readPixels" and weight = 22.43 and contextType = "webgl" ) + ( this = "getSupportedExtensions" and weight = 1484.82 and contextType = "webgl" ) or - ( this = "isPointInPath" and weight = 5210.68 and contextType = "2d" ) + ( this = "isPointInPath" and weight = 4255.55 and contextType = "2d" ) or - ( this = "readPixels" and weight = 610.19 and contextType = "webgl2" ) + ( this = "readPixels" and weight = 1004.16 and contextType = "webgl2" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpSensorProperty.qll b/.github/codeql/queries/autogen_fpSensorProperty.qll index 74bf3e4f988..bfc5c329068 100644 --- a/.github/codeql/queries/autogen_fpSensorProperty.qll +++ b/.github/codeql/queries/autogen_fpSensorProperty.qll @@ -6,7 +6,7 @@ class SensorProperty extends string { SensorProperty() { - ( this = "start" and weight = 92.53 ) + ( this = "start" and weight = 105.54 ) } float getWeight() { diff --git a/metadata/modules.json b/metadata/modules.json index 6040fedcd66..ed326efe85d 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -106,6 +106,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "adcluster", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "addefend", @@ -575,6 +582,13 @@ "gvlid": 779, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "adrubi", + "aliasOf": "admatic", + "gvlid": 779, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "yobee", @@ -2297,6 +2311,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "floxis", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "fluct", @@ -2486,6 +2507,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "harion", + "aliasOf": null, + "gvlid": 1406, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "holid", @@ -2591,6 +2619,13 @@ "gvlid": 910, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "insurads", + "aliasOf": null, + "gvlid": 596, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "integr8", @@ -2745,6 +2780,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "leagueM", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "lemmadigital", @@ -3984,6 +4026,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "revantage", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "revcontent", @@ -4768,6 +4817,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "verben", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "viant", diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 6b45e117a54..4b610a461c3 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-02-12T16:01:21.045Z", + "timestamp": "2026-02-23T16:45:39.806Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index d132e4c8ce6..0ce2459449b 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-02-12T16:01:21.129Z", + "timestamp": "2026-02-23T16:45:39.909Z", "disclosures": [] } }, diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index 7e2ecfaf0aa..7b98e95a50d 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-02-12T16:01:21.131Z", + "timestamp": "2026-02-23T16:45:39.911Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index 76d4597f2e0..92c90104c38 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-02-12T16:01:21.165Z", + "timestamp": "2026-02-23T16:45:39.945Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index a3d401fe5fe..7c93f685dc4 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-02-12T16:01:21.235Z", + "timestamp": "2026-02-23T16:45:40.036Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index ede1de11239..a6dcdef0b1c 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-02-12T16:01:21.235Z", + "timestamp": "2026-02-23T16:45:40.036Z", "disclosures": [] } }, diff --git a/metadata/modules/adclusterBidAdapter.json b/metadata/modules/adclusterBidAdapter.json new file mode 100644 index 00000000000..72d44231bb6 --- /dev/null +++ b/metadata/modules/adclusterBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "adcluster", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index 833ced84879..df69a9d5540 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-02-12T16:01:21.528Z", + "timestamp": "2026-02-23T16:45:40.351Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index ee585e4e1d2..ab8a61481e8 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-02-12T16:01:22.179Z", + "timestamp": "2026-02-23T16:45:41.307Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 3e61c86c500..668b5b6c97a 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-02-12T16:01:22.179Z", + "timestamp": "2026-02-23T16:45:41.308Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index f940e855d92..092ee382d70 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-02-12T16:01:22.535Z", + "timestamp": "2026-02-23T16:45:41.663Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 6c4231222ae..036a1961d11 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-02-06T14:30:27.081Z", + "timestamp": "2026-02-23T16:45:41.943Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index b0735f2d02c..b0b98e5cb55 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-02-12T16:02:24.306Z", + "timestamp": "2026-02-23T16:45:42.098Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index 699b609b82c..959d9b275e9 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-02-12T16:02:24.365Z", + "timestamp": "2026-02-23T16:45:42.136Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,19 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:24.365Z", + "timestamp": "2026-02-23T16:45:42.136Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2026-02-12T16:02:24.405Z", + "timestamp": "2026-02-23T16:45:42.198Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:25.119Z", + "timestamp": "2026-02-23T16:45:42.896Z", "disclosures": [] }, "https://appmonsta.ai/DeviceStorageDisclosure.json": { - "timestamp": "2026-02-12T16:02:25.139Z", + "timestamp": "2026-02-23T16:45:42.917Z", "disclosures": [] } }, diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index 65efb208dab..e2473a7371a 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-02-12T16:02:27.007Z", + "timestamp": "2026-02-23T16:45:43.437Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:26.382Z", + "timestamp": "2026-02-23T16:45:43.437Z", "disclosures": [ { "identifier": "adt_pbjs", @@ -65,6 +65,13 @@ "gvlid": 779, "disclosureURL": "https://adtarget.com.tr/.well-known/deviceStorage.json" }, + { + "componentType": "bidder", + "componentName": "adrubi", + "aliasOf": "admatic", + "gvlid": 779, + "disclosureURL": "https://adtarget.com.tr/.well-known/deviceStorage.json" + }, { "componentType": "bidder", "componentName": "yobee", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index e4e03a255c2..0f2f9e230e4 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-02-12T16:02:27.008Z", + "timestamp": "2026-02-23T16:45:43.438Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index 7fbc4ee3566..6b14f9eb08b 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-02-12T16:02:27.393Z", + "timestamp": "2026-02-23T16:45:43.822Z", "disclosures": [] } }, diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index f8ca11154ef..7dea0044c43 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-02-12T16:02:27.393Z", + "timestamp": "2026-02-23T16:45:43.822Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index add99b58b5d..1f3b4f7666c 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-02-12T16:02:27.629Z", + "timestamp": "2026-02-23T16:45:44.057Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index 113c9ae2ff2..39503071928 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-02-12T16:02:27.943Z", + "timestamp": "2026-02-23T16:45:44.386Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index a08f919d96d..1aa5c4d8b9d 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-02-12T16:02:27.943Z", + "timestamp": "2026-02-23T16:45:44.386Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index 485bca89b63..abfdb8fd8a0 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-02-12T16:02:28.385Z", + "timestamp": "2026-02-23T16:45:44.944Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index f0d44894055..fb09981c236 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-02-12T16:02:28.420Z", + "timestamp": "2026-02-23T16:45:44.982Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index f0f50dd336c..481d7d8641f 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-02-12T16:02:28.442Z", + "timestamp": "2026-02-23T16:45:45.005Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index cc3c8ebcdc9..856d53a7cc3 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-02-12T16:02:28.771Z", + "timestamp": "2026-02-23T16:45:45.339Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 7c4868198ae..53fabc3f2ac 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-02-12T16:02:28.771Z", + "timestamp": "2026-02-23T16:45:45.339Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index 0dff5161e3d..647a9890f50 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-02-12T16:02:28.818Z", + "timestamp": "2026-02-23T16:45:45.472Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index 001aa2a7f51..2f48665cbf8 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-02-12T16:02:29.106Z", + "timestamp": "2026-02-23T16:45:45.991Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index 6e0fbeb92f5..b35112b9b94 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-02-12T16:02:29.107Z", + "timestamp": "2026-02-23T16:45:45.991Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { - "timestamp": "2026-02-12T16:02:29.126Z", + "timestamp": "2026-02-23T16:45:46.006Z", "disclosures": [ { "identifier": "waterFallCacheAnsKey_*", @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-02-12T16:02:29.264Z", + "timestamp": "2026-02-23T16:45:46.157Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index 455ad975236..13229a05a8a 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-02-12T16:02:29.327Z", + "timestamp": "2026-02-23T16:45:46.235Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index 3531e290667..5920e80f15a 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-02-12T16:02:29.328Z", + "timestamp": "2026-02-23T16:45:46.235Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index eb03744a92b..87844aa07e8 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-02-12T16:02:29.344Z", + "timestamp": "2026-02-23T16:45:46.253Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index da7f96629b9..de1bb8ebb1a 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-02-12T16:02:29.835Z", + "timestamp": "2026-02-23T16:45:46.717Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index 1095f525817..cd39fe92629 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-02-12T16:02:29.872Z", + "timestamp": "2026-02-23T16:45:46.745Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json index e598d7c4933..09be6ca136f 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-02-12T16:02:30.160Z", + "timestamp": "2026-02-23T16:45:47.028Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index ef1ecf75a2c..2a860a2aaba 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-02-12T16:02:30.497Z", + "timestamp": "2026-02-23T16:45:47.485Z", "disclosures": [] } }, diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index 0dad1a537b8..7b6aafe01dd 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-02-12T16:02:30.697Z", + "timestamp": "2026-02-23T16:45:47.524Z", "disclosures": [] } }, diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index be7ed16896f..b8deecd8258 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-02-12T16:02:30.697Z", + "timestamp": "2026-02-23T16:45:47.524Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 5ac6c462286..8260cbedc78 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-02-12T16:02:30.857Z", + "timestamp": "2026-02-23T16:45:47.614Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index 5cfb1c756bf..4cca97a2c61 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-02-12T16:02:31.124Z", + "timestamp": "2026-02-23T16:45:47.819Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index 7bd5d6a37b0..59823893662 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-02-12T16:02:31.149Z", + "timestamp": "2026-02-23T16:45:47.845Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index eb1dc56263c..575475e3f1d 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-02-12T16:02:31.741Z", + "timestamp": "2026-02-23T16:45:48.607Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:31.253Z", + "timestamp": "2026-02-23T16:45:48.018Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2026-02-12T16:02:31.267Z", + "timestamp": "2026-02-23T16:45:48.146Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2026-02-12T16:02:31.741Z", + "timestamp": "2026-02-23T16:45:48.607Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index 8bf8b136a69..f08a1d0abc5 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-02-06T14:30:35.522Z", + "timestamp": "2026-02-23T16:45:48.625Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json index e952350795e..c5f467eb5b9 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-02-12T16:02:34.413Z", + "timestamp": "2026-02-23T16:45:48.698Z", "disclosures": [ { "identifier": "vendor-id", diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index f079f9c51e2..51abe574f23 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-02-12T16:02:34.492Z", + "timestamp": "2026-02-23T16:45:48.905Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index d635f3e4a03..425d6c403b5 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-02-12T16:02:34.539Z", + "timestamp": "2026-02-23T16:45:48.947Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index 121e4823898..3c7f723af4d 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-02-12T16:02:34.590Z", + "timestamp": "2026-02-23T16:45:49.244Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 96e58341aeb..7d4a7482075 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-02-12T16:02:34.629Z", + "timestamp": "2026-02-23T16:45:49.284Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index 347041b1771..bca8af342af 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-02-12T16:02:34.649Z", + "timestamp": "2026-02-23T16:45:49.309Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 1ac67c45bb3..4abf52c58f3 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-02-12T16:02:34.770Z", + "timestamp": "2026-02-23T16:45:49.662Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index 3131d3f0280..7b7c771fb56 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-02-12T16:02:34.932Z", + "timestamp": "2026-02-23T16:45:49.787Z", "disclosures": [] } }, diff --git a/metadata/modules/bidfuseBidAdapter.json b/metadata/modules/bidfuseBidAdapter.json index e74a1ffac1d..8ec350d8e5f 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-02-12T16:02:35.044Z", + "timestamp": "2026-02-23T16:45:49.844Z", "disclosures": [] } }, diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index 835f6023ba9..28932b2ac27 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-02-12T16:02:35.282Z", + "timestamp": "2026-02-23T16:45:50.078Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index 22dd0477647..a221774de81 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-02-12T16:02:35.574Z", + "timestamp": "2026-02-23T16:45:50.372Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index c1fc7ab720b..6ca672e3894 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-02-12T16:02:35.919Z", + "timestamp": "2026-02-23T16:45:50.669Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index 6afd8f94190..08accddbd72 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-02-12T16:02:36.114Z", + "timestamp": "2026-02-23T16:45:50.841Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index 3cd530e9e52..7d4f03a43f3 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-02-12T16:02:36.469Z", + "timestamp": "2026-02-23T16:45:51.242Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index 6eecd3713b1..b99fd90c052 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-02-12T16:02:36.485Z", + "timestamp": "2026-02-23T16:45:51.273Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 584c32aaed4..5e83165008b 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-02-12T16:02:36.505Z", + "timestamp": "2026-02-23T16:45:51.292Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index 75288b13c07..202ed38c166 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-02-12T16:02:36.642Z", + "timestamp": "2026-02-23T16:45:51.438Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index 5362e0d7616..c63668fe614 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-02-12T16:02:36.724Z", + "timestamp": "2026-02-23T16:45:51.522Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index ba4b665569c..d87a42ad403 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-02-12T16:02:36.802Z", + "timestamp": "2026-02-23T16:45:51.927Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index 63b7fa8dd17..3283b263af3 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-02-12T16:01:21.043Z", + "timestamp": "2026-02-23T16:45:39.804Z", "disclosures": [ { "identifier": "iabToFwMappingkey", diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 21d4f41a957..e64878ea4cb 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-02-12T16:02:37.113Z", + "timestamp": "2026-02-23T16:45:52.241Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index 8b67521da78..53cb3b5385d 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-02-12T16:02:37.432Z", + "timestamp": "2026-02-23T16:45:52.571Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index c55807312c7..6d53c6e853b 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-02-12T16:02:37.433Z", + "timestamp": "2026-02-23T16:45:52.572Z", "disclosures": [] } }, diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index 28f73b8fc54..b244dc6ce23 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-02-12T16:02:37.869Z", + "timestamp": "2026-02-23T16:45:52.988Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index 4120a3e79e8..18aafeda009 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-02-12T16:02:37.883Z", + "timestamp": "2026-02-23T16:45:53.004Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index 38ed5b48065..7683ce8538d 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-02-12T16:02:37.900Z", + "timestamp": "2026-02-23T16:45:53.031Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index ca873f0a826..4a923ffa33f 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-02-12T16:02:37.979Z", + "timestamp": "2026-02-23T16:45:53.109Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index c634ba2b78d..a0d2b9aed0c 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-02-12T16:02:38.131Z", + "timestamp": "2026-02-23T16:45:53.187Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index 8d4a2682cf1..242e014398c 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-02-12T16:02:38.566Z", + "timestamp": "2026-02-23T16:45:53.622Z", "disclosures": null } }, diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index ba1ed814056..f6f6faad13d 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-02-12T16:02:38.617Z", + "timestamp": "2026-02-23T16:45:54.632Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 5489f28f93c..7448ea8dcd0 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-02-12T16:02:38.638Z", + "timestamp": "2026-02-23T16:45:54.677Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index 0d54181878f..affad4ec7c7 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-02-12T16:02:38.680Z", + "timestamp": "2026-02-23T16:45:54.802Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index 3fcb47d695a..5520f75a569 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-02-12T16:02:38.751Z", + "timestamp": "2026-02-23T16:45:54.838Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index 794e32b3530..4faac441ed3 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-02-12T16:02:38.765Z", + "timestamp": "2026-02-23T16:45:54.851Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index 58e4ad62902..4b04b4f76e2 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-02-12T16:02:38.765Z", + "timestamp": "2026-02-23T16:45:54.851Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index a98ac284c2b..6dfc197ae34 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-02-12T16:02:39.176Z", + "timestamp": "2026-02-23T16:45:54.952Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index b1c9bbacfcb..9ea7adb6c9e 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-02-12T16:02:39.581Z", + "timestamp": "2026-02-23T16:45:55.362Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index 49688eb84bf..239dc523417 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-02-12T16:01:21.042Z", + "timestamp": "2026-02-23T16:45:39.803Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index 2221de60f09..f9698c4095a 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-02-12T16:02:39.601Z", + "timestamp": "2026-02-23T16:45:55.384Z", "disclosures": [] } }, diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index 8de882d42c7..41142e3663e 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-02-12T16:02:39.715Z", + "timestamp": "2026-02-23T16:45:55.469Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index 9edd9e160a4..f2fb3929675 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-02-12T16:02:40.051Z", + "timestamp": "2026-02-23T16:45:55.817Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index 79386ea311a..864b1e03246 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-02-12T16:02:40.494Z", + "timestamp": "2026-02-23T16:45:56.279Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 34fedbd9f35..5e6606fb5a4 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-02-12T16:02:40.494Z", + "timestamp": "2026-02-23T16:45:56.279Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index 037fcb0f0b5..455b19c7a00 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-02-12T16:02:40.844Z", + "timestamp": "2026-02-23T16:46:10.165Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index bcc0c82f9ec..924aa537c52 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-02-12T16:02:41.046Z", + "timestamp": "2026-02-23T16:46:10.392Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index 516d5a7785e..f49cf65cfdb 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-02-12T16:02:41.844Z", + "timestamp": "2026-02-23T16:46:11.142Z", "disclosures": [] } }, diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 190947ae279..9b5a628f3ee 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-02-12T16:02:41.845Z", + "timestamp": "2026-02-23T16:46:11.143Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 6d812474b8c..32dff8c4b9f 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-02-12T16:02:42.492Z", + "timestamp": "2026-02-23T16:46:11.788Z", "disclosures": [] } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index d0ef514410f..7675689c619 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-02-12T16:02:42.834Z", + "timestamp": "2026-02-23T16:46:12.128Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index cb7626d1369..1f472770d2f 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-02-12T16:02:42.856Z", + "timestamp": "2026-02-23T16:46:12.175Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index fbb5d460a1a..b80b81ce7d1 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-02-12T16:02:42.881Z", + "timestamp": "2026-02-23T16:46:12.205Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index 810edf72a42..05a2cfaf124 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-02-12T16:02:43.575Z", + "timestamp": "2026-02-23T16:46:12.239Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index 6ba6e67e677..7954882ec63 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-02-12T16:02:43.599Z", + "timestamp": "2026-02-23T16:46:12.258Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 3f69a0588ed..52f7c75e179 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-02-12T16:02:44.156Z", + "timestamp": "2026-02-23T16:46:12.836Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 1c9285b1f1b..072c7c51e68 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-02-12T16:02:44.363Z", + "timestamp": "2026-02-23T16:46:13.078Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index dfd8673dc84..5b852097ea3 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-02-12T16:02:44.545Z", + "timestamp": "2026-02-23T16:46:16.911Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/floxisBidAdapter.json b/metadata/modules/floxisBidAdapter.json new file mode 100644 index 00000000000..c58d76bc57a --- /dev/null +++ b/metadata/modules/floxisBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "floxis", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index a72ecdf4f2d..fe615c7a678 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-02-12T16:02:44.662Z", + "timestamp": "2026-02-23T16:46:17.030Z", "disclosures": [] } }, diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index 704895f09fb..c2cc3a39847 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-02-12T16:02:45.420Z", + "timestamp": "2026-02-23T16:46:19.113Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 4d26cdccf71..4e1a3568be9 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-02-12T16:02:45.421Z", + "timestamp": "2026-02-23T16:46:19.114Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 52f0a606c6a..266a3a47920 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-02-12T16:02:45.438Z", + "timestamp": "2026-02-23T16:46:19.139Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index 90e3ab8f3e7..fbebc8a5b71 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-02-12T16:02:45.558Z", + "timestamp": "2026-02-23T16:46:19.169Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index 293b1fa4a36..9a89dd3db7b 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-02-12T16:02:45.608Z", + "timestamp": "2026-02-23T16:46:19.328Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index 34e8e955301..d2faf64c6e6 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-02-12T16:02:45.661Z", + "timestamp": "2026-02-23T16:46:19.392Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index 1f2a4dbb797..93ec7d55d3d 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-02-12T16:02:45.769Z", + "timestamp": "2026-02-23T16:46:19.493Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/harionBidAdapter.json b/metadata/modules/harionBidAdapter.json new file mode 100644 index 00000000000..6a7fe43d137 --- /dev/null +++ b/metadata/modules/harionBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://markappmedia.site/vendor.json": { + "timestamp": "2026-02-23T16:46:19.494Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "harion", + "aliasOf": null, + "gvlid": 1406, + "disclosureURL": "https://markappmedia.site/vendor.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index 68fd9d2d6b3..9e5f60d4e75 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-02-12T16:02:45.769Z", + "timestamp": "2026-02-23T16:46:19.844Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index 01d35b6a911..b65b5d8d4a8 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-02-12T16:02:46.021Z", + "timestamp": "2026-02-23T16:46:20.115Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index b98fb1af422..a67ab589a06 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-02-12T16:02:46.234Z", + "timestamp": "2026-02-23T16:46:20.421Z", "disclosures": [ { "identifier": "id5id", diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index 1538b610067..3a611244109 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-02-12T16:02:46.511Z", + "timestamp": "2026-02-23T16:46:20.700Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index ec1a068ee0d..7c87299d926 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-02-12T16:02:46.527Z", + "timestamp": "2026-02-23T16:46:20.718Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index e96641ebd0f..a6aaeabad08 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-02-12T16:02:46.819Z", + "timestamp": "2026-02-23T16:46:21.047Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 19175d650d1..0c7a398cffb 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-02-12T16:02:47.118Z", + "timestamp": "2026-02-23T16:46:21.326Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index cbe9285062b..acd77ff74fc 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-02-12T16:02:47.119Z", + "timestamp": "2026-02-23T16:46:21.327Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index 3727ed73c99..f19aa191649 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-02-12T16:02:47.155Z", + "timestamp": "2026-02-23T16:46:21.362Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/insuradsBidAdapter.json b/metadata/modules/insuradsBidAdapter.json new file mode 100644 index 00000000000..cbd5502eed4 --- /dev/null +++ b/metadata/modules/insuradsBidAdapter.json @@ -0,0 +1,45 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://www.insurads.com/tcf-vdsod.json": { + "timestamp": "2026-02-23T16:46:21.432Z", + "disclosures": [ + { + "identifier": "___iat_ses", + "type": "cookie", + "maxAgeSeconds": 1800, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 7, + 9, + 10 + ] + }, + { + "identifier": "___iat_vis", + "type": "cookie", + "maxAgeSeconds": 15552000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 7, + 9, + 10 + ] + } + ] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "insurads", + "aliasOf": null, + "gvlid": 596, + "disclosureURL": "https://www.insurads.com/tcf-vdsod.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 8dd39780a97..09702d55af8 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-02-12T16:02:47.252Z", + "timestamp": "2026-02-23T16:46:21.553Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index a8e51b19f3f..cce1631883e 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-02-12T16:02:47.320Z", + "timestamp": "2026-02-23T16:46:21.605Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 6e052cc095c..95e81ce7106 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-02-12T16:02:47.724Z", + "timestamp": "2026-02-23T16:46:21.954Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index 56c8a86c393..d77169ebf76 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-02-12T16:02:48.192Z", + "timestamp": "2026-02-23T16:46:22.404Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index 4720743b59f..7158bab3abe 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-02-12T16:02:48.474Z", + "timestamp": "2026-02-23T16:46:25.271Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index 9032eff563e..6fcc8ee22ea 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-02-12T16:02:48.995Z", + "timestamp": "2026-02-23T16:46:25.791Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 1e1b658c5e9..38ed4cfc3fc 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-02-12T16:02:49.014Z", + "timestamp": "2026-02-23T16:46:25.812Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index 59cd15df11a..b520862c201 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-02-12T16:02:49.531Z", + "timestamp": "2026-02-23T16:46:26.041Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index 600bbc90614..886600913c3 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-02-12T16:02:49.549Z", + "timestamp": "2026-02-23T16:46:26.065Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/leagueMBidAdapter.json b/metadata/modules/leagueMBidAdapter.json new file mode 100644 index 00000000000..f4b02875bbb --- /dev/null +++ b/metadata/modules/leagueMBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "leagueM", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index 8b758c99045..8431c4f79f5 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-02-12T16:02:49.605Z", + "timestamp": "2026-02-23T16:46:26.136Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-02-12T16:02:49.634Z", + "timestamp": "2026-02-23T16:46:26.206Z", "disclosures": [] } }, diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index f9a792d368e..f60e74c1825 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-02-12T16:02:49.635Z", + "timestamp": "2026-02-23T16:46:26.206Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index e578376282c..d0bc2c5c444 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-02-12T16:02:49.742Z", + "timestamp": "2026-02-23T16:46:26.218Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 3465a663f7c..1cd5d2d128c 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-02-12T16:02:49.742Z", + "timestamp": "2026-02-23T16:46:26.219Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index 64695e94be7..fee1f412589 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-02-12T16:02:49.765Z", + "timestamp": "2026-02-23T16:46:26.249Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 939e167a88e..1019ad44e25 100644 --- a/metadata/modules/lotamePanoramaIdSystem.json +++ b/metadata/modules/lotamePanoramaIdSystem.json @@ -2,8 +2,65 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tags.crwdcntrl.net/privacy/tcf-purposes.json": { - "timestamp": "2026-02-12T16:02:49.810Z", + "timestamp": "2026-02-23T16:46:26.732Z", "disclosures": [ + { + "identifier": "_cc_id", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "_cc_cc", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "identifier": "_cc_aud", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, { "identifier": "lotame_domain_check", "type": "cookie", @@ -23,6 +80,25 @@ 11 ] }, + { + "identifier": "_pubcid", + "type": "cookie", + "maxAgeSeconds": 23328000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, { "identifier": "panoramaId", "type": "web", @@ -124,6 +200,23 @@ 10, 11 ] + }, + { + "identifier": "_pubcid", + "type": "web", + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] } ] } diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 309fbb9c764..61100089726 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-02-12T16:02:49.821Z", + "timestamp": "2026-02-23T16:46:26.798Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 54448980b48..990477cdb7b 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-02-12T16:02:50.270Z", + "timestamp": "2026-02-23T16:46:27.273Z", "disclosures": [] } }, diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index 4f5d92e72c8..ba0e333fa38 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-02-12T16:02:50.627Z", + "timestamp": "2026-02-23T16:46:27.628Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index 86274d64c09..7c796c8155c 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-02-12T16:02:50.730Z", + "timestamp": "2026-02-23T16:46:27.759Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index 773a89af4e4..b2cc478cf54 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-02-12T16:02:50.856Z", + "timestamp": "2026-02-23T16:46:28.044Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index e0336bf74b0..74390863c39 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-02-12T16:02:50.873Z", + "timestamp": "2026-02-23T16:46:28.059Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index 29bb2e9f87b..9d59e87ea4a 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-02-12T16:02:50.873Z", + "timestamp": "2026-02-23T16:46:28.059Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index 3f36882d419..2a5e75627b9 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-02-12T16:02:51.028Z", + "timestamp": "2026-02-23T16:46:28.120Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index 8a970c473b0..5a1c732c5de 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-02-12T16:02:51.317Z", + "timestamp": "2026-02-23T16:46:28.405Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:51.350Z", + "timestamp": "2026-02-23T16:46:28.464Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index 6287f359f73..aac7269dadc 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-02-12T16:02:51.416Z", + "timestamp": "2026-02-23T16:46:29.446Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index 07cbf00b9cd..667c32cd604 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-02-12T16:02:51.997Z", + "timestamp": "2026-02-23T16:46:29.993Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index 9be8a4bb9c9..550a82a422c 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-02-12T16:02:52.143Z", + "timestamp": "2026-02-23T16:46:30.399Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index da2be5c07f3..554cc915038 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-02-12T16:02:52.144Z", + "timestamp": "2026-02-23T16:46:30.399Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index 66cdc973443..f031c7ba114 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-02-12T16:02:52.144Z", + "timestamp": "2026-02-23T16:46:30.399Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index ecc39bf9622..ebc86cfe7ce 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-02-12T16:02:52.163Z", + "timestamp": "2026-02-23T16:46:30.434Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index aa8853a6ceb..a942bc0147f 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-02-12T16:02:52.213Z", + "timestamp": "2026-02-23T16:46:30.523Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index 77fe7c78637..ecada798667 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-02-06T14:30:52.437Z", - "disclosures": [] + "timestamp": "2026-02-23T16:46:30.655Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index e863c308a77..e9b37f0ed11 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-02-06T14:30:52.457Z", - "disclosures": [] + "timestamp": "2026-02-23T16:46:30.691Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index ca925bf2471..d862ff06784 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-02-12T16:02:52.249Z", + "timestamp": "2026-02-23T16:46:30.691Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index 851a3b990cd..5cf3f47c826 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-02-12T16:02:52.250Z", + "timestamp": "2026-02-23T16:46:30.699Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index f28730a66da..833339739a4 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-02-12T16:02:52.540Z", + "timestamp": "2026-02-23T16:46:31.031Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index 0ff9f940a54..ef9353ef688 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-02-12T16:02:52.571Z", + "timestamp": "2026-02-23T16:46:31.054Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index 6543164833b..8ee05edf5e9 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-02-12T16:02:52.571Z", + "timestamp": "2026-02-23T16:46:31.054Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index f321b7167c3..fd4d8551d20 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-02-12T16:02:52.634Z", + "timestamp": "2026-02-23T16:46:31.132Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 57388e46056..32c4f7b07fa 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-02-12T16:02:53.677Z", + "timestamp": "2026-02-23T16:46:32.174Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2026-02-12T16:02:52.936Z", + "timestamp": "2026-02-23T16:46:31.398Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:52.957Z", + "timestamp": "2026-02-23T16:46:31.421Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-02-12T16:02:53.279Z", + "timestamp": "2026-02-23T16:46:31.648Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,7 +46,7 @@ ] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2026-02-12T16:02:53.279Z", + "timestamp": "2026-02-23T16:46:31.648Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -61,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2026-02-12T16:02:53.323Z", + "timestamp": "2026-02-23T16:46:31.797Z", "disclosures": [] } }, diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index e92563c1f50..d697808a02e 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-02-12T16:02:53.678Z", + "timestamp": "2026-02-23T16:46:32.174Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 759fe8b18ce..5fac928bc8b 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-02-12T16:02:53.738Z", + "timestamp": "2026-02-23T16:46:32.338Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index 93d3a1fa9b7..a5fa44903e0 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-02-12T16:02:55.028Z", + "timestamp": "2026-02-23T16:46:34.054Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index 981602b4ce4..4fcb8df58e4 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-02-12T16:02:55.374Z", + "timestamp": "2026-02-23T16:46:34.443Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index 60a58583769..8eb0796b3c8 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-02-12T16:02:55.425Z", + "timestamp": "2026-02-23T16:46:34.510Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index 316573e7fec..101bcbf8ef2 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-02-12T16:02:55.477Z", + "timestamp": "2026-02-23T16:46:34.569Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 38ab0910d1a..508199c4060 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-02-12T16:02:55.478Z", + "timestamp": "2026-02-23T16:46:34.570Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index 9ae87308b00..ffa1a04f0a0 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-02-12T16:02:55.752Z", + "timestamp": "2026-02-23T16:46:34.912Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index ebffa088907..d7dd135c570 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-02-12T16:02:55.798Z", + "timestamp": "2026-02-23T16:46:34.950Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index 70a56662aa2..cfa23f46ee5 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-02-12T16:02:55.862Z", + "timestamp": "2026-02-23T16:46:34.995Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index dae623cfa1f..40c446b1728 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-02-12T16:02:55.986Z", + "timestamp": "2026-02-23T16:46:35.155Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index bc88deae6d2..66165aa5587 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-02-12T16:02:56.036Z", + "timestamp": "2026-02-23T16:46:35.204Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index 659e17e9530..1d348223a0f 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-02-12T16:02:56.292Z", + "timestamp": "2026-02-23T16:46:35.462Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index eeca4f76f9c..32d23be29fa 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-02-12T16:02:56.598Z", + "timestamp": "2026-02-23T16:46:35.776Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index a8bcfa12a8d..cc8ac6fa341 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-02-12T16:02:57.005Z", + "timestamp": "2026-02-23T16:46:35.993Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index 9628c7cb901..8e8904cf7ff 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-02-12T16:02:57.215Z", + "timestamp": "2026-02-23T16:46:36.196Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json index ee5ddbd0194..559f347c9a8 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-02-12T16:02:57.240Z", + "timestamp": "2026-02-23T16:46:36.216Z", "disclosures": [ { "identifier": "panxo_uid", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index b938b5ff679..4b4f986fab5 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-02-12T16:02:57.442Z", + "timestamp": "2026-02-23T16:46:36.601Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index b9f72106517..7e186152215 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-02-12T16:02:57.860Z", + "timestamp": "2026-02-23T16:46:37.007Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 723d480174e..9f953274741 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-02-12T16:02:58.052Z", + "timestamp": "2026-02-23T16:46:37.198Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index 69fbaa7a887..6b5ede1ed9f 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-02-12T16:02:58.053Z", + "timestamp": "2026-02-23T16:46:37.199Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index 9b0628358d5..4fefc69bc62 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-02-12T16:02:58.117Z", + "timestamp": "2026-02-23T16:46:37.264Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 1d41698b72d..80a6aa15d93 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-02-12T16:01:21.041Z", + "timestamp": "2026-02-23T16:45:39.802Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-02-12T16:01:21.041Z", + "timestamp": "2026-02-23T16:45:39.802Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index f012780e482..d1f28334f85 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-02-12T16:02:58.386Z", + "timestamp": "2026-02-23T16:46:37.437Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index 2a737339ed3..9c67c55703e 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-02-12T16:02:58.624Z", + "timestamp": "2026-02-23T16:46:37.668Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index 76e2feedefe..f73f75e6a2e 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-02-12T16:02:58.624Z", + "timestamp": "2026-02-23T16:46:37.670Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 80fe8881336..788c756279d 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-02-12T16:02:58.677Z", + "timestamp": "2026-02-23T16:46:37.731Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index 5e3655a928a..bb87745635d 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-02-12T16:02:59.056Z", + "timestamp": "2026-02-23T16:46:38.272Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index cc7ff1faf97..d1ceb3861d2 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-02-12T16:02:59.056Z", + "timestamp": "2026-02-23T16:46:38.273Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index 51219cb48e3..96de8153bd2 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-02-12T16:02:59.077Z", + "timestamp": "2026-02-23T16:46:38.326Z", "disclosures": [] } }, diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index a98b8a3ac3e..a99d1500aef 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-02-12T16:02:59.079Z", + "timestamp": "2026-02-23T16:46:38.328Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index 48901f9abd5..d5376ad540f 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-02-12T16:02:59.096Z", + "timestamp": "2026-02-23T16:46:38.345Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json index d5ccbda5d6a..c4824387f0e 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-02-12T16:02:59.281Z", + "timestamp": "2026-02-23T16:46:38.525Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index 65d8b0a5330..c30e8a48fb0 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-02-12T16:02:59.282Z", + "timestamp": "2026-02-23T16:46:38.536Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index d2a1df795fc..52d1e7e94ed 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-02-12T16:02:59.711Z", + "timestamp": "2026-02-23T16:46:38.907Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index 08f83b4d67e..f8b1ebdb359 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-02-12T16:02:59.733Z", + "timestamp": "2026-02-23T16:46:38.943Z", "disclosures": null } }, diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index dadb0d9bb9e..012ce5e965e 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-02-12T16:03:00.349Z", + "timestamp": "2026-02-23T16:46:39.813Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index 6a0636bf2db..0f76baa147d 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-02-12T16:03:00.637Z", + "timestamp": "2026-02-23T16:46:40.101Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index 35b5b2fa851..bc05a7b7e84 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-02-12T16:03:00.674Z", + "timestamp": "2026-02-23T16:46:40.139Z", "disclosures": [] } }, diff --git a/metadata/modules/revantageBidAdapter.json b/metadata/modules/revantageBidAdapter.json new file mode 100644 index 00000000000..90eda1e36ad --- /dev/null +++ b/metadata/modules/revantageBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "revantage", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index 849e6939e78..f632ce44325 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-02-06T14:31:01.772Z", + "timestamp": "2026-02-23T16:46:40.186Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json index 54a016fc087..5ecb6aa4bf9 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-02-12T16:03:03.287Z", + "timestamp": "2026-02-23T16:46:40.251Z", "disclosures": [] } }, diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index a4e0dd2a9eb..cc403f3bc41 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-02-12T16:03:03.363Z", + "timestamp": "2026-02-23T16:46:40.374Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index 3f0e4c6bb92..a4356846e9c 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-02-12T16:03:03.611Z", + "timestamp": "2026-02-23T16:46:40.704Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index c7c3e3e0890..f91ed10f678 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-02-12T16:03:03.679Z", + "timestamp": "2026-02-23T16:46:40.772Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-02-12T16:03:03.679Z", + "timestamp": "2026-02-23T16:46:40.772Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 348c6dcb13d..7350dc1a269 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-02-12T16:03:03.680Z", + "timestamp": "2026-02-23T16:46:40.772Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index c88664db9cf..2ce97f53637 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-02-12T16:03:03.748Z", + "timestamp": "2026-02-23T16:46:40.789Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index d93e37f56f3..17e1ff321e2 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-02-12T16:03:03.959Z", + "timestamp": "2026-02-23T16:46:41.009Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index 0fd5d0b2c04..9ad2e289dc2 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-02-12T16:03:04.286Z", + "timestamp": "2026-02-23T16:46:41.251Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 2a8bb316e56..612712787f7 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-02-12T16:03:04.297Z", + "timestamp": "2026-02-23T16:46:41.270Z", "disclosures": null } }, diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index 5c25dc8810c..a5cae979bb5 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-02-12T16:03:06.889Z", + "timestamp": "2026-02-23T16:46:43.868Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index 2e020d25478..994b6f1a103 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-02-12T16:03:06.917Z", + "timestamp": "2026-02-23T16:46:43.971Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index ce8c7bd0c41..cc7548e32e5 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-02-12T16:03:06.917Z", + "timestamp": "2026-02-23T16:46:43.971Z", "disclosures": [] } }, diff --git a/metadata/modules/setupadBidAdapter.json b/metadata/modules/setupadBidAdapter.json index 525865a79f5..4411f2126c2 100644 --- a/metadata/modules/setupadBidAdapter.json +++ b/metadata/modules/setupadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cookies.stpd.cloud/disclosures.json": { - "timestamp": "2026-02-12T16:03:06.974Z", + "timestamp": "2026-02-23T16:46:44.040Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index bcc4dea773b..8b4155bad91 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-02-12T16:03:07.118Z", + "timestamp": "2026-02-23T16:46:44.168Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index ffe3e1e442c..d387202b43c 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-02-12T16:03:07.268Z", + "timestamp": "2026-02-23T16:46:44.313Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index da0c0755306..89c00185e6e 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-02-12T16:03:07.269Z", + "timestamp": "2026-02-23T16:46:44.313Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index fe4ceeb1210..9c73af19048 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-02-12T16:03:07.288Z", + "timestamp": "2026-02-23T16:46:44.338Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index f286d4d51a1..3226ae43a1f 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-02-12T16:03:07.730Z", + "timestamp": "2026-02-23T16:46:44.845Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index 46e87be7f40..0c54f8de6de 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-02-12T16:03:07.750Z", + "timestamp": "2026-02-23T16:46:44.860Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index bf69227423d..3d218dd6936 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-02-12T16:03:08.062Z", + "timestamp": "2026-02-23T16:46:45.210Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index 759da109d28..b17d0a46919 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-02-12T16:03:08.141Z", + "timestamp": "2026-02-23T16:46:45.331Z", "disclosures": [] } }, diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 7224b550bbe..56af6c1fe90 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-02-12T16:03:08.141Z", + "timestamp": "2026-02-23T16:46:45.331Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index af55cd0f240..eba853e8c5f 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-02-12T16:03:08.171Z", + "timestamp": "2026-02-23T16:46:45.348Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index bc742e4f6b8..ac23b467ca0 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-02-12T16:03:08.208Z", + "timestamp": "2026-02-23T16:46:45.388Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index b4b5c679170..92f87aff58a 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-02-12T16:03:08.654Z", + "timestamp": "2026-02-23T16:46:45.853Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index c8b89653bdf..6ae3c2d0521 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-02-12T16:03:08.694Z", + "timestamp": "2026-02-23T16:46:45.887Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index 722452ff6d5..9af60365ea1 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-02-12T16:03:08.919Z", + "timestamp": "2026-02-23T16:46:46.110Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index 2101385b9ea..0cfeb10dd67 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-02-12T16:03:09.152Z", + "timestamp": "2026-02-23T16:46:46.339Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index 1c7f8dac007..fd0a4545adb 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-02-12T16:03:09.171Z", + "timestamp": "2026-02-23T16:46:46.358Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index 8f31daf486f..74e7b5f7b4e 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-02-12T16:03:09.446Z", + "timestamp": "2026-02-23T16:46:46.867Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index 84a556c6201..6751dd820fb 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-02-12T16:03:10.095Z", + "timestamp": "2026-02-23T16:46:47.561Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index ab45236aeda..805fbbb0997 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-02-12T16:03:10.098Z", + "timestamp": "2026-02-23T16:46:47.562Z", "disclosures": [ { "identifier": "sa-camp-*", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index dc49c45a89c..bd992ea772c 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-02-12T16:03:10.140Z", + "timestamp": "2026-02-23T16:46:47.608Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index ab81914ddbc..d198134cd6b 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-02-12T16:03:10.159Z", + "timestamp": "2026-02-23T16:46:47.628Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 603ed9c0ac3..1f780caaf60 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-02-12T16:03:10.485Z", + "timestamp": "2026-02-23T16:46:48.003Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index 95eac12b349..ec6552c31a3 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-02-12T16:03:11.171Z", + "timestamp": "2026-02-23T16:46:48.633Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index 4c40074b3a5..04e6ee9448c 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-02-12T16:03:11.428Z", + "timestamp": "2026-02-23T16:46:48.896Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index e1b062e5330..facd96483c0 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-02-12T16:03:11.606Z", + "timestamp": "2026-02-23T16:46:49.649Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index 4dcff6012e3..2883fb9359b 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-02-12T16:03:11.607Z", + "timestamp": "2026-02-23T16:46:49.649Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index eff67d69fee..80a5faf1393 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-02-12T16:03:11.628Z", + "timestamp": "2026-02-23T16:46:49.676Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index be8fab01e12..9d2f8d9eb10 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-02-12T16:03:11.652Z", + "timestamp": "2026-02-23T16:46:49.705Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index 4ff824ac847..f7bde1849e1 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-02-12T16:03:11.652Z", + "timestamp": "2026-02-23T16:46:49.705Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index ea550951d4f..e87eaea62c9 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-02-12T16:03:11.668Z", + "timestamp": "2026-02-23T16:46:49.723Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index 1546530cad5..9e70bdfb084 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-02-12T16:03:11.668Z", + "timestamp": "2026-02-23T16:46:49.723Z", "disclosures": [] } }, diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index b7ad8bb19c2..eda93c2d7e9 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-02-12T16:03:11.707Z", + "timestamp": "2026-02-23T16:46:49.770Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index c6323ff867a..80539abb3c4 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-02-12T16:01:21.042Z", + "timestamp": "2026-02-23T16:45:39.803Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index 94e13fc1264..9da24e24f42 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-02-12T16:03:11.723Z", + "timestamp": "2026-02-23T16:46:49.802Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index ae405a1f660..749692885ff 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-02-12T16:03:11.778Z", + "timestamp": "2026-02-23T16:46:49.901Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index 1a97705723d..a867b85ec31 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-02-12T16:03:11.803Z", + "timestamp": "2026-02-23T16:46:49.930Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index 0fe3b3c63fc..559fb3fd5c4 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-02-12T16:03:11.804Z", + "timestamp": "2026-02-23T16:46:49.930Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index 514b2957082..2628349ae18 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-02-12T16:03:11.906Z", + "timestamp": "2026-02-23T16:47:13.633Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index 6734f167b02..a358ef5eba5 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-02-12T16:03:11.948Z", + "timestamp": "2026-02-23T16:47:13.661Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index 26a129bb323..9d874319e3e 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-02-12T16:03:12.044Z", + "timestamp": "2026-02-23T16:47:13.761Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index 368b184d3a9..bb45d2ed3b7 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-02-12T16:03:12.044Z", + "timestamp": "2026-02-23T16:47:13.762Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index 131897b5f8b..0ba454ed268 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-02-12T16:01:21.043Z", + "timestamp": "2026-02-23T16:45:39.804Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index 1a922646a89..07224a7afd7 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-02-12T16:03:12.044Z", + "timestamp": "2026-02-23T16:47:13.762Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index c60259b5cf0..4a4449b21b1 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-02-12T16:03:12.045Z", + "timestamp": "2026-02-23T16:47:13.763Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index 6502dfce25e..eda44e25e76 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-02-12T16:01:21.042Z", + "timestamp": "2026-02-23T16:45:39.803Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index 09da3afbf0f..fe471a8ca81 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-02-12T16:03:12.045Z", + "timestamp": "2026-02-23T16:47:13.763Z", "disclosures": [] } }, diff --git a/metadata/modules/verbenBidAdapter.json b/metadata/modules/verbenBidAdapter.json new file mode 100644 index 00000000000..47a939ac97a --- /dev/null +++ b/metadata/modules/verbenBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "verben", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index 7cc5addfeb0..d893b26cc36 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-02-12T16:03:12.248Z", + "timestamp": "2026-02-23T16:47:13.996Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index 27d85fecd48..3def3f7d4eb 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-02-12T16:03:12.307Z", + "timestamp": "2026-02-23T16:47:14.066Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index a20652521ca..6a52ca53499 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-02-12T16:03:12.426Z", + "timestamp": "2026-02-23T16:47:14.187Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 4849247b507..077ee35fa36 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-02-12T16:03:12.427Z", + "timestamp": "2026-02-23T16:47:14.188Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index f80b9decb3e..2ae5d926cdc 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-02-12T16:03:12.612Z", + "timestamp": "2026-02-23T16:47:14.524Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index d3596fcacde..ccf35b682bb 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-02-12T16:03:12.933Z", + "timestamp": "2026-02-23T16:47:14.873Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 0cb70229dc3..7677600a53e 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-02-12T16:03:12.933Z", + "timestamp": "2026-02-23T16:47:14.873Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index 646e64d9296..9bc73add65e 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-02-12T16:03:12.947Z", + "timestamp": "2026-02-23T16:47:14.886Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 0a93cb5ba6d..2a8011b6b1f 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://weborama.com/deviceStorage.json": { - "timestamp": "2026-02-12T16:03:13.279Z", + "timestamp": "2026-02-23T16:47:15.183Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index b8d08fb7017..8d10a15c1c2 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-02-12T16:03:13.532Z", + "timestamp": "2026-02-23T16:47:15.439Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index d0cf7b2b937..749e127536f 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-02-12T16:03:13.912Z", + "timestamp": "2026-02-23T16:47:15.869Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yaleoBidAdapter.json b/metadata/modules/yaleoBidAdapter.json index 6d26861fb39..dd76827e542 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-02-12T16:03:13.913Z", + "timestamp": "2026-02-23T16:47:15.869Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index 145db3cb888..6f6e9c3db9c 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-02-12T16:03:13.913Z", + "timestamp": "2026-02-23T16:47:15.870Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index f48aaed9a51..81f282fcb98 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-02-12T16:03:14.044Z", + "timestamp": "2026-02-23T16:47:15.998Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index 15b590bed34..03d2e47b63e 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-02-12T16:03:14.062Z", + "timestamp": "2026-02-23T16:47:16.022Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index da29d640798..9f3ad140787 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-02-12T16:03:14.143Z", + "timestamp": "2026-02-23T16:47:16.089Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index 5b918202094..291c37e9782 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-02-12T16:03:14.262Z", + "timestamp": "2026-02-23T16:47:16.223Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index e114466444a..11d006cca2a 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-02-12T16:03:14.357Z", + "timestamp": "2026-02-23T16:47:16.313Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index 7ebcabde344..3ac8558beac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.26.0-pre", + "version": "10.26.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.26.0-pre", + "version": "10.26.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", @@ -7441,11 +7441,14 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/basic-auth": { @@ -7877,9 +7880,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", "funding": [ { "type": "opencollective", @@ -27044,9 +27047,9 @@ "dev": true }, "baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==" + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==" }, "basic-auth": { "version": "2.0.1", @@ -27339,9 +27342,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==" + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==" }, "chai": { "version": "4.4.1", diff --git a/package.json b/package.json index 4d0c20c0d1a..92025df5f0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.26.0-pre", + "version": "10.26.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 8dc0819f436470b6d31de70ab88b1dcc939249f4 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 23 Feb 2026 16:54:41 +0000 Subject: [PATCH 073/105] Increment version to 10.27.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 3ac8558beac..def9330652b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.26.0", + "version": "10.27.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.26.0", + "version": "10.27.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 92025df5f0d..daf5b472e5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.26.0", + "version": "10.27.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From d59df0d9c28844ec798dd4cdb52c7999c52406ec Mon Sep 17 00:00:00 2001 From: driftpixelai <166716541+driftpixelai@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:13:32 +0200 Subject: [PATCH 074/105] DPAI bid adapter: initial release (#14434) * New adapter DPAI * New adapter DPAI * New adapter DPAI: Added end line * New adapter DPAI: Added end line --------- Co-authored-by: Patrick McCann --- modules/dpaiBidAdapter.js | 19 + modules/dpaiBidAdapter.md | 79 ++++ test/spec/modules/dpaiBidAdapter_spec.js | 513 +++++++++++++++++++++++ 3 files changed, 611 insertions(+) create mode 100644 modules/dpaiBidAdapter.js create mode 100644 modules/dpaiBidAdapter.md create mode 100644 test/spec/modules/dpaiBidAdapter_spec.js diff --git a/modules/dpaiBidAdapter.js b/modules/dpaiBidAdapter.js new file mode 100644 index 00000000000..4cf359b196b --- /dev/null +++ b/modules/dpaiBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'dpai'; +const AD_URL = 'https://ssp.drift-pixel.ai/pbjs'; +const SYNC_URL = 'https://sync.drift-pixel.ai'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/dpaiBidAdapter.md b/modules/dpaiBidAdapter.md new file mode 100644 index 00000000000..4882abdacc4 --- /dev/null +++ b/modules/dpaiBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: DPAI Bidder Adapter +Module Type: DPAI Bidder Adapter +Maintainer: adops@driftpixel.ai +``` + +# Description + +Connects to DPAI exchange for bids. +DPAI bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'dpai', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'dpai', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'dpai', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/dpaiBidAdapter_spec.js b/test/spec/modules/dpaiBidAdapter_spec.js new file mode 100644 index 00000000000..f412a3316f0 --- /dev/null +++ b/test/spec/modules/dpaiBidAdapter_spec.js @@ -0,0 +1,513 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/dpaiBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'dpai'; + +describe('DpaiBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK', + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + const serverRequest = spec.buildRequests(bids, bidderRequest); + const data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + const dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + const dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + const nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + const dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + const serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + const serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + const serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.drift-pixel.ai/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.drift-pixel.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.drift-pixel.ai/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); From c4c923e87ff81d0e56e6a5e13b4f15fec1225a21 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 23 Feb 2026 18:19:34 -0500 Subject: [PATCH 075/105] Core: remove stale transformBidParams references (#14512) --- libraries/ortbConverter/README.md | 2 +- libraries/pbsExtensions/processors/pbs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ortbConverter/README.md b/libraries/ortbConverter/README.md index c67533ae1de..691ff7bceb7 100644 --- a/libraries/ortbConverter/README.md +++ b/libraries/ortbConverter/README.md @@ -378,7 +378,7 @@ For ease of use, the conversion logic gives special meaning to some context prop ## Prebid Server extensions -If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. +If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params, bidder aliases, targeting keys, and others. ```javascript import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js' diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js index 3fa97ae674b..82a99954646 100644 --- a/libraries/pbsExtensions/processors/pbs.js +++ b/libraries/pbsExtensions/processors/pbs.js @@ -30,7 +30,7 @@ export const PBS_PROCESSORS = { }, [IMP]: { params: { - // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params, passed through transformBidParams if necessary + // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params fn: setImpBidParams }, adUnitCode: { From 30c1cfe439403839946d23c25a81676d033a4507 Mon Sep 17 00:00:00 2001 From: briguy-mobian Date: Thu, 26 Feb 2026 22:44:02 +0700 Subject: [PATCH 076/105] docs: adding documentation for mobianMpaaRating, mobianContentTaxonomy, mobianEsrbRating (#14513) --- modules/mobianRtdProvider.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md index ccfc43e3aab..dee9cc12773 100644 --- a/modules/mobianRtdProvider.md +++ b/modules/mobianRtdProvider.md @@ -160,6 +160,36 @@ p1 = Advertisers (via Campaign IDs) should target these personas *AP Values is in the early stages of testing and is subject to change. +------------------ + +Additional Results Fields (API response) + +The fields below are present in the Mobian Contextual API `results` schema and are useful for downstream interpretation of content maturity and taxonomy. + +mobianMpaaRating: + +Type: integer | null + +Description: MPAA-style maturity rating score represented as an integer value in the API response. + +Behavior when unavailable: omitted when null. + +mobianEsrbRating: + +Type: integer | null + +Description: ESRB-style maturity rating score represented as an integer value in the API response. + +Behavior when unavailable: omitted when null. + +mobianContentTaxonomy: + +Type: string[] + +Description: IAB content taxonomy categories (broad topic buckets such as "News" or "Health"). + +Behavior when unavailable: may be returned as an empty array. + ## GAM Targeting: On each page load, the Mobian RTD module finds each ad slot on the page and performs the following function: From 410e87e1cf340f4466e2687b3401edfbb98648d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:01:39 -0500 Subject: [PATCH 077/105] Bump minimatch (#14520) Bumps and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together. Updates `minimatch` from 3.1.2 to 3.1.4 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.4) Updates `minimatch` from 9.0.5 to 9.0.7 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.4) Updates `minimatch` from 5.1.6 to 5.1.8 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.4) Updates `minimatch` from 9.0.4 to 9.0.7 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.4) Updates `minimatch` from 10.0.3 to 10.2.3 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.4) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.4 dependency-type: indirect - dependency-name: minimatch dependency-version: 9.0.7 dependency-type: indirect - dependency-name: minimatch dependency-version: 5.1.8 dependency-type: indirect - dependency-name: minimatch dependency-version: 9.0.7 dependency-type: indirect - dependency-name: minimatch dependency-version: 10.2.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 339 +++++++++++++++++++++++++++++----------------- 1 file changed, 211 insertions(+), 128 deletions(-) diff --git a/package-lock.json b/package-lock.json index def9330652b..c0693ef3397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2967,28 +2967,6 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "dev": true, @@ -3980,24 +3958,34 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -10512,6 +10500,27 @@ } } }, + "node_modules/eslint-plugin-import-x/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint-plugin-import-x/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/eslint-plugin-import-x/node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -10531,16 +10540,15 @@ } }, "node_modules/eslint-plugin-import-x/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "dev": true, - "license": "ISC", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11586,11 +11594,10 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -12272,22 +12279,34 @@ "node": ">= 10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -15483,6 +15502,15 @@ "webpack": "^5.0.0" } }, + "node_modules/karma-webpack/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/karma-webpack/node_modules/glob": { "version": "7.2.3", "dev": true, @@ -15503,9 +15531,10 @@ } }, "node_modules/karma-webpack/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -15514,11 +15543,12 @@ } }, "node_modules/karma-webpack/node_modules/minimatch": { - "version": "9.0.4", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -15528,13 +15558,15 @@ } }, "node_modules/karma-webpack/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/karma/node_modules/ansi-styles": { @@ -16170,9 +16202,10 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -16436,9 +16469,10 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -16648,22 +16682,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/multimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/multimatch/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/multimatch/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -18263,9 +18309,10 @@ } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -24086,21 +24133,6 @@ "dev": true, "requires": {} }, - "@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true - }, - "@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "requires": { - "@isaacs/balanced-match": "^4.0.1" - } - }, "@isaacs/cliui": { "version": "8.0.2", "dev": true, @@ -24773,22 +24805,28 @@ "ts-api-utils": "^2.1.0" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } }, "semver": { @@ -29183,6 +29221,21 @@ "unrs-resolver": "^1.9.2" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, "debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -29193,12 +29246,12 @@ } }, "minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "dev": true, "requires": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" } }, "ms": { @@ -29759,9 +29812,9 @@ } }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -30154,20 +30207,28 @@ "path-scurry": "^1.11.1" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "9.0.5", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } } } @@ -32361,6 +32422,12 @@ "webpack-merge": "^4.1.5" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "glob": { "version": "7.2.3", "dev": true, @@ -32374,7 +32441,9 @@ }, "dependencies": { "minimatch": { - "version": "3.1.2", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -32383,19 +32452,21 @@ } }, "minimatch": { - "version": "9.0.4", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "dependencies": { "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } } } @@ -32745,7 +32816,9 @@ } }, "minimatch": { - "version": "3.1.2", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -32913,7 +32986,9 @@ } }, "minimatch": { - "version": "5.1.6", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -33048,22 +33123,28 @@ "minimatch": "^9.0.3" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } } } @@ -34056,7 +34137,9 @@ } }, "minimatch": { - "version": "5.1.6", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "dev": true, "requires": { "brace-expansion": "^2.0.1" From db117a9d3ae85cb945bc04c555e9cff937b09d95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:02:03 -0500 Subject: [PATCH 078/105] Bump basic-ftp from 5.0.5 to 5.2.0 (#14522) Bumps [basic-ftp](https://github.com/patrickjuchli/basic-ftp) from 5.0.5 to 5.2.0. - [Release notes](https://github.com/patrickjuchli/basic-ftp/releases) - [Changelog](https://github.com/patrickjuchli/basic-ftp/blob/master/CHANGELOG.md) - [Commits](https://github.com/patrickjuchli/basic-ftp/compare/v5.0.5...v5.2.0) --- updated-dependencies: - dependency-name: basic-ftp dependency-version: 5.2.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0693ef3397..8da1c6e41dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7456,9 +7456,10 @@ "license": "MIT" }, "node_modules/basic-ftp": { - "version": "5.0.5", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -27103,7 +27104,9 @@ } }, "basic-ftp": { - "version": "5.0.5", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", "dev": true }, "batch": { From 6101561b72f30962caa207f50e0cdcdc28193087 Mon Sep 17 00:00:00 2001 From: Paul Farrow Date: Thu, 26 Feb 2026 16:06:25 +0000 Subject: [PATCH 079/105] Chrome AI RTD Provider: fix QuotaExceededError with large page content (#14295) * Chrome AI RTD Provider: fix QuotaExceededError with large page content Added MAX_TEXT_LENGTH constant (1000 chars) and text truncation logic in getPageText() to prevent QuotaExceededError when Chrome AI APIs process pages with large amounts of text content. Without this fix, pages with extensive text content can cause the Chrome AI APIs (LanguageDetector, Summarizer) to throw QuotaExceededError exceptions. * Chrome AI RTD Provider: add unit tests for MAX_TEXT_LENGTH and text truncation Adds tests covering: - MAX_TEXT_LENGTH constant exists and equals 1000 - getPageText returns null for text below MIN_TEXT_LENGTH - getPageText returns null for empty text - getPageText returns full text when between MIN and MAX length - getPageText does not truncate text at exactly MAX_TEXT_LENGTH - getPageText truncates text exceeding MAX_TEXT_LENGTH - getPageText logs a message when truncating Co-authored-by: Cursor --------- Co-authored-by: Paul Farrow Co-authored-by: Cursor --- modules/chromeAiRtdProvider.js | 6 ++ test/spec/modules/chromeAiRtdProvider_spec.js | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/modules/chromeAiRtdProvider.js b/modules/chromeAiRtdProvider.js index 9fd2d4c6639..75f17b6312a 100644 --- a/modules/chromeAiRtdProvider.js +++ b/modules/chromeAiRtdProvider.js @@ -15,6 +15,7 @@ export const CONSTANTS = Object.freeze({ STORAGE_KEY: 'chromeAi_detected_data', // Single key for both language and keywords MIN_TEXT_LENGTH: 20, ACTIVATION_EVENTS: ['click', 'keydown', 'mousedown', 'touchend', 'pointerdown', 'pointerup'], + MAX_TEXT_LENGTH: 1000, // Limit to prevent QuotaExceededError with Chrome AI APIs DEFAULT_CONFIG: { languageDetector: { enabled: true, @@ -94,6 +95,11 @@ export const getPageText = () => { logMessage(`${CONSTANTS.LOG_PRE_FIX} Not enough text content (length: ${text?.length || 0}) for processing.`); return null; } + // Limit text length to prevent QuotaExceededError with Chrome AI APIs + if (text.length > CONSTANTS.MAX_TEXT_LENGTH) { + logMessage(`${CONSTANTS.LOG_PRE_FIX} Truncating text from ${text.length} to ${CONSTANTS.MAX_TEXT_LENGTH} chars.`); + return text.substring(0, CONSTANTS.MAX_TEXT_LENGTH); + } return text; }; diff --git a/test/spec/modules/chromeAiRtdProvider_spec.js b/test/spec/modules/chromeAiRtdProvider_spec.js index 744f09ed4df..dfbf454bff9 100644 --- a/test/spec/modules/chromeAiRtdProvider_spec.js +++ b/test/spec/modules/chromeAiRtdProvider_spec.js @@ -135,6 +135,71 @@ describe('Chrome AI RTD Provider', function () { expect(chromeAiRtdProvider.CONSTANTS.SUBMODULE_NAME).to.equal('chromeAi'); expect(chromeAiRtdProvider.CONSTANTS.STORAGE_KEY).to.equal('chromeAi_detected_data'); expect(chromeAiRtdProvider.CONSTANTS.MIN_TEXT_LENGTH).to.be.a('number'); + expect(chromeAiRtdProvider.CONSTANTS.MAX_TEXT_LENGTH).to.be.a('number'); + expect(chromeAiRtdProvider.CONSTANTS.MAX_TEXT_LENGTH).to.equal(1000); + }); + }); + + // Test getPageText and text truncation + describe('getPageText (text truncation)', function () { + // Override document.body.textContent via Object.defineProperty so we can + // control the value returned to getPageText() without mutating the actual + // DOM (which would break the Karma test-runner UI). + function setBodyText(text) { + Object.defineProperty(document.body, 'textContent', { + get: () => text, + configurable: true + }); + } + + afterEach(function () { + // Remove the instance-level override to restore the inherited getter + delete document.body.textContent; + }); + + it('should return null for text shorter than MIN_TEXT_LENGTH', function () { + setBodyText('short'); + const result = chromeAiRtdProvider.getPageText(); + expect(result).to.be.null; + expect(logMessageStub.calledWith(sinon.match('Not enough text content'))).to.be.true; + }); + + it('should return null for empty text', function () { + setBodyText(''); + const result = chromeAiRtdProvider.getPageText(); + expect(result).to.be.null; + }); + + it('should return full text when length is between MIN and MAX', function () { + const text = 'A'.repeat(500); + setBodyText(text); + const result = chromeAiRtdProvider.getPageText(); + expect(result).to.equal(text); + expect(result).to.have.lengthOf(500); + }); + + it('should return text at exactly MAX_TEXT_LENGTH without truncating', function () { + const exactText = 'B'.repeat(chromeAiRtdProvider.CONSTANTS.MAX_TEXT_LENGTH); + setBodyText(exactText); + const result = chromeAiRtdProvider.getPageText(); + expect(result).to.equal(exactText); + expect(logMessageStub.calledWith(sinon.match('Truncating'))).to.be.false; + }); + + it('should truncate text exceeding MAX_TEXT_LENGTH', function () { + const longText = 'C'.repeat(2000); + setBodyText(longText); + const result = chromeAiRtdProvider.getPageText(); + expect(result).to.have.lengthOf(chromeAiRtdProvider.CONSTANTS.MAX_TEXT_LENGTH); + expect(result).to.equal('C'.repeat(1000)); + }); + + it('should log a message when truncating text', function () { + setBodyText('D'.repeat(2000)); + chromeAiRtdProvider.getPageText(); + expect(logMessageStub.calledWith( + sinon.match('Truncating text from 2000 to 1000') + )).to.be.true; }); }); From a718fce894b9266125eebf7b00f38a97b9f0f06d Mon Sep 17 00:00:00 2001 From: gregneuwo Date: Thu, 26 Feb 2026 17:08:05 +0100 Subject: [PATCH 080/105] Neuwo Rtd Module: Version v2.0.0 and Quality of Life Improvements (#14323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add live display and access examples of Neuwo API data to example page * feat: add Prebid.js event timing tests to Neuwo RTD example page * style: standardize code style in Neuwo RTD example - Convert single quotes to double quotes for consistency - Format code with Prettier * docs: add guide for accessing Neuwo RTD data outside Prebid.js * feat: add concurrent bid request handling to Neuwo RTD Module * chore: standardize log message format in Neuwo RTD Module * feat: add product identifier to Neuwo API requests * docs: fix typo in test file name in Neuwo RTD Module documentation * style: format Neuwo RTD Module with Prettier - Add `// prettier-ignore` comment to preserve quoted keys in `IAB_CONTENT_TAXONOMY_MAP` - Apply Prettier formatting to *modules/neuwoRtdProvider.js* * test: add coverage for concurrent requests and product identifier in Neuwo RTD Module * feat: add per-tier IAB taxonomy filtering to Neuwo RTD Module Add optional filtering configuration for IAB Content and Audience taxonomies, allowing publishers to control the quantity and quality of categories injected into bid requests. - Add `iabTaxonomyFilters` parameter to module configuration for per-tier filtering - Implement `filterIabTaxonomyTier()` function to filter taxonomies by relevance threshold and limit count - Implement `filterIabTaxonomies()` function to apply filters across all tiers using tier key mapping - Add integration example checkbox to enable/disable filtering with hardcoded filter values * docs: add IAB taxonomy filtering documentation to Neuwo RTD Module * test: add error handling and edge case tests for Neuwo RTD Module - Add test for API URL construction when `neuwoApiUrl` contains existing query parameters - Add tests for error response handling: 404 errors, pending request cleanup, and retry behaviour - Add tests for concurrent requests with errors ensuring all callbacks are invoked - Add tests for JSON parsing errors in success callback - Add test for retry after JSON parsing error - Add test for missing `marketing_categories` field in API response - Add test for sorting items with undefined/null relevance values - Reorganise test structure: nest caching, URL stripping, and filtering describes under main `getBidRequestData` block - Add documentation for generating test coverage reports with viewing instructions * style: apply formatting to Neuwo RTD Module markdown documentation * refactor: remove name field from IAB segments in ORTB2 output - Update `buildIabData()` to only include `id` in segment objects - Remove `label` requirement; segments now only need valid `ID` - Update documentation examples to reflect simplified segment structure - Update unit tests to match new segment format * feat: migrate Neuwo RTD Module to new API architecture (v2.0.0) and maintain backward compatibility - Add version 2.0.0 with multi-version IAB Content Taxonomy support - Implement automatic API capability detection from endpoint URL format - Maintain backward compatibility with legacy GET endpoints - Consolidate default IAB Content Taxonomy version to "2.2" constant - Transform legacy API responses to unified segtax-based format - Add `buildIabFilterConfig()` for filter configuration conversion - Add `transformV1ResponseToV2()` and `transformSegmentsV1ToV2()` helpers - Refactor `buildIabData()` to dynamically process all tiers - Update `injectIabCategories()` to work with unified response format - Enhance JSDoc documentation with format examples and API details - Add conditional logging for POST request bodies - Optimise filtering: apply once before caching for legacy endpoints * test: update Neuwo RTD Module tests for new API architecture - Update all test mocks to use new segtax-based response format - Add tests for `buildIabFilterConfig()` function - Add tests for `injectIabCategories()` function - Add tests for `transformV1ResponseToV2()` function - Add tests for `transformSegmentsV1ToV2()` function - Update `buildIabData()` tests to use new tierData parameter structure - Add V1 API backward compatibility test suite with client-side filtering tests - Verify POST method and iabVersions query parameters for new API - Verify GET method and no iabVersions parameter for legacy V1 API - Update edge case tests for empty responses and error handling - Update integration tests for URL parameter handling with query strings - Remove hardcoded tier array constants (CONTENT_TIERS, AUDIENCE_TIERS) * docs: update Neuwo RTD Module documentation for new API version - Change default IAB Content Taxonomy version from "3.0" to "2.2" - Add IAB Content Taxonomy version "1.0" to supported values list - Add server-side filtering explanation to IAB Taxonomy Filtering section - Update Available Tiers table to focus on taxonomy types instead of API internals - Add recommended configuration comments for `auctionDelay` and `waitForIt` - Update all configuration examples to use "2.2" as default taxonomy version - Remove "How it works" section * feat: add OpenRTB 2.5 category fields support to Neuwo RTD Module Add support for populating OpenRTB 2.5 category fields (`site.cat`, `site.sectioncat`, `site.pagecat`, `site.content.cat`) with IAB Content Taxonomy 1.0 segments. Changes: - Add `enableOrtb25Fields` configuration parameter (default: true) - Add `extractCategoryIds()` helper function to extract segment IDs from tier data - Refactor `buildIabData()` to use `extractCategoryIds()` for code reuse - Extend `buildIabFilterConfig()` to apply filters to IAB 1.0 when feature enabled - Modify `getBidRequestData()` to request IAB 1.0 data (segtax 1) when feature enabled - Add warning when feature enabled with legacy API endpoint - Extend `injectIabCategories()` to populate four OpenRTB 2.5 category fields - Update version to 2.1.0 * ci: add OpenRTB 2.5 category fields support to integration example Changes: - Add checkbox UI control for `enableOrtb25Fields` option (default: checked) - Add display section for OpenRTB 2.5 category fields data - Extract and display `site.cat`, `site.sectioncat`, `site.pagecat`, and `site.content.cat` fields - Update `bidRequested` event handler to capture category fields - Add localStorage persistence for `enableOrtb25Fields` setting - Pass `enableOrtb25Fields` parameter to module configuration * test: add unit tests for OpenRTB 2.5 category fields Changes: - Add 11 new `buildIabFilterConfig()` tests for OpenRTB 2.5 feature (enabled/disabled scenarios) - Add 11 new `extractCategoryIds()` tests covering all edge cases - Add 11 new `injectIabCategories()` tests for category field injection - Update 5 existing `buildIabFilterConfig()` tests - Add 6 new `getBidRequestData()` integration tests for V2 API with feature enabled/disabled - Move legacy API compatibility tests from V2 section to V1 section * docs: add OpenRTB 2.5 category fields support documentation to Neuwo RTD Module Changes: - Add OpenRTB 2.5 feature description to module overview - Add `enableOrtb25Fields` parameter to parameters table - Add dedicated "OpenRTB 2.5 Category Fields" section with examples - Update ORTB2 data structure examples to show category fields - Add filtering section explaining IAB 1.0 filter application - Update "Accessing Neuwo Data" section with category field extraction example - Add "Building for Production" section with build command - Update segtax value in example (7 → 6) - Update version to 2.1.0 * perf: change Content-Type to text/plain to avoid CORS preflight * refactor: change /v1/iab endpoint from POST to GET with flattened query params Replace POST request with GET and send IAB taxonomy filters as flattened URL query parameters instead of JSON body to avoid CORS preflight requests. Changes: - Replace `buildIabFilterConfig()` with `buildFilterQueryParams()` function - Change output from nested object to array of query parameter strings (e.g., `["filter_6_1_limit=3"]`) - Remove POST method and request body from ajax call - Add filter parameters directly to URL for /v1/iab endpoint - Maintain OpenRTB 2.5 filter application (ContentTier1/2 → segtax 1) - Update all unit tests to test `buildFilterQueryParams()` instead of `buildIabFilterConfig()` - Update tests expecting POST requests to expect GET requests - Update tests checking request body to check URL parameters - Update version to 2.2.0 * feat: key cache by full API URL to support config changes between auctions - Replace global `cachedResponses` and `pendingRequests` singletons with objects keyed by `neuwoApiUrlFull` - Concurrent or sequential calls with different parameters (URL, taxonomy version, filters) maintain separate cache entries - Add LRU-style eviction capped at 10 entries to prevent unbounded cache growth - Log module version during `init()` - Add unit tests for cache isolation * fix: guard empty URLs, inject content/audience independently, allow `limit: 0` to suppress tiers - Separate JSON parsing from response processing into distinct try/catch blocks - Add `Array.isArray` guard to `extractCategoryIds` - Only cache valid object responses so failed requests can be retried - Rename `isV2Api` to `isIabEndpoint` for clarity - Fix typo in log message and JSDoc (POST -> GET) - Bump to 2.2.1 * fix: isolate legacy endpoint cache keys by taxonomy version and filters * fix: prevent duplicate filter query params when IAB Content Taxonomy is 1.0 * fix: only sort and filter taxonomy tiers when filter is configured * fix: detect /v1/iab endpoint from URL pathname instead of full string * fix: prevent duplicate `iabVersions=1` query param when content taxonomy is 1.0 --------- Co-authored-by: grzgm <125459798+grzgm@users.noreply.github.com> Co-authored-by: gregneuwo <226034698+gregneuwo@users.noreply.github.com> --- .../gpt/neuwoRtdProvider_example.html | 439 +- modules/neuwoRtdProvider.js | 793 ++- modules/neuwoRtdProvider.md | 279 +- test/spec/modules/neuwoRtdProvider_spec.js | 4465 +++++++++++++---- 4 files changed, 4891 insertions(+), 1085 deletions(-) diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index 3d6fef98995..68c95fe8b4f 100644 --- a/integrationExamples/gpt/neuwoRtdProvider_example.html +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -26,28 +26,28 @@ var adUnits = [ { - code: '/19968336/header-bid-tag-1', + code: "/19968336/header-bid-tag-1", mediaTypes: { banner: { sizes: div_1_sizes } }, bids: [{ - bidder: 'appnexus', + bidder: "appnexus", params: { placementId: 13144370 } }] }, { - code: '/19968336/header-bid-tag-1', + code: "/19968336/header-bid-tag-1", mediaTypes: { banner: { sizes: div_2_sizes } }, bids: [{ - bidder: 'appnexus', + bidder: "appnexus", params: { placementId: 13144370 } @@ -86,12 +86,12 @@ // Custom Timeout logic in onSettingsUpdate() googletag.cmd.push(function () { - googletag.defineSlot('/19968336/header-bid-tag-1', div_1_sizes, 'div-1').addService(googletag.pubads()); + googletag.defineSlot("/19968336/header-bid-tag-1", div_1_sizes, "div-1").addService(googletag.pubads()); googletag.pubads().enableSingleRequest(); googletag.enableServices(); }); googletag.cmd.push(function () { - googletag.defineSlot('/19968336/header-bid-tag-1', div_2_sizes, 'div-2').addService(googletag.pubads()); + googletag.defineSlot("/19968336/header-bid-tag-1", div_2_sizes, "div-2").addService(googletag.pubads()); googletag.pubads().enableSingleRequest(); googletag.enableServices(); }); @@ -99,47 +99,65 @@ // 3. User Triggered Setup (RTD Module) function onSettingsUpdate() { - const inputNeuwoApiToken = document.getElementById('neuwo-api-token'); - const neuwoApiToken = inputNeuwoApiToken ? inputNeuwoApiToken.value : ''; + const inputNeuwoApiToken = document.getElementById("neuwo-api-token"); + const neuwoApiToken = inputNeuwoApiToken ? inputNeuwoApiToken.value : ""; if (!neuwoApiToken) { - alert('Please enter your token for Neuwo AI API to the field'); + alert("Please enter your token for Neuwo AI API to the field"); if (inputNeuwoApiToken) inputNeuwoApiToken.focus(); return; } - const inputNeuwoApiUrl = document.getElementById('neuwo-api-url'); - const neuwoApiUrl = inputNeuwoApiUrl ? inputNeuwoApiUrl.value : ''; + const inputNeuwoApiUrl = document.getElementById("neuwo-api-url"); + const neuwoApiUrl = inputNeuwoApiUrl ? inputNeuwoApiUrl.value : ""; if (!neuwoApiUrl) { - alert('Please enter Neuwo AI API url to the field'); + alert("Please enter Neuwo AI API url to the field"); if (inputNeuwoApiUrl) inputNeuwoApiUrl.focus(); return; } - const inputWebsiteToAnalyseUrl = document.getElementById('website-to-analyse-url'); + const inputWebsiteToAnalyseUrl = document.getElementById("website-to-analyse-url"); const websiteToAnalyseUrl = inputWebsiteToAnalyseUrl ? inputWebsiteToAnalyseUrl.value : undefined; - const inputIabContentTaxonomyVersion = document.getElementById('iab-content-taxonomy-version'); + const inputIabContentTaxonomyVersion = document.getElementById("iab-content-taxonomy-version"); const iabContentTaxonomyVersion = inputIabContentTaxonomyVersion ? inputIabContentTaxonomyVersion.value : undefined; // Cache Option - const inputEnableCache = document.getElementById('enable-cache'); + const inputEnableCache = document.getElementById("enable-cache"); const enableCache = inputEnableCache ? inputEnableCache.checked : undefined; + // OpenRTB 2.5 Category Fields Option + const inputEnableOrtb25Fields = document.getElementById("enable-ortb25-fields"); + const enableOrtb25Fields = inputEnableOrtb25Fields ? inputEnableOrtb25Fields.checked : true; + // URL Stripping Options - const inputStripAllQueryParams = document.getElementById('strip-all-query-params'); + const inputStripAllQueryParams = document.getElementById("strip-all-query-params"); const stripAllQueryParams = inputStripAllQueryParams ? inputStripAllQueryParams.checked : undefined; - const inputStripQueryParamsForDomains = document.getElementById('strip-query-params-for-domains'); - const stripQueryParamsForDomainsValue = inputStripQueryParamsForDomains ? inputStripQueryParamsForDomains.value.trim() : ''; - const stripQueryParamsForDomains = stripQueryParamsForDomainsValue ? stripQueryParamsForDomainsValue.split(',').map(d => d.trim()).filter(d => d) : undefined; + const inputStripQueryParamsForDomains = document.getElementById("strip-query-params-for-domains"); + const stripQueryParamsForDomainsValue = inputStripQueryParamsForDomains ? inputStripQueryParamsForDomains.value.trim() : ""; + const stripQueryParamsForDomains = stripQueryParamsForDomainsValue ? stripQueryParamsForDomainsValue.split(",").map(d => d.trim()).filter(d => d) : undefined; - const inputStripQueryParams = document.getElementById('strip-query-params'); - const stripQueryParamsValue = inputStripQueryParams ? inputStripQueryParams.value.trim() : ''; - const stripQueryParams = stripQueryParamsValue ? stripQueryParamsValue.split(',').map(p => p.trim()).filter(p => p) : undefined; + const inputStripQueryParams = document.getElementById("strip-query-params"); + const stripQueryParamsValue = inputStripQueryParams ? inputStripQueryParams.value.trim() : ""; + const stripQueryParams = stripQueryParamsValue ? stripQueryParamsValue.split(",").map(p => p.trim()).filter(p => p) : undefined; - const inputStripFragments = document.getElementById('strip-fragments'); + const inputStripFragments = document.getElementById("strip-fragments"); const stripFragments = inputStripFragments ? inputStripFragments.checked : undefined; + // IAB Taxonomy Filtering Option + const inputEnableFiltering = document.getElementById("enable-iab-filtering"); + const enableIabFiltering = inputEnableFiltering ? inputEnableFiltering.checked : false; + + // Build iabTaxonomyFilters object only if filtering is enabled + const iabTaxonomyFilters = enableIabFiltering ? { + ContentTier1: { limit: 1, threshold: 0.1 }, + ContentTier2: { limit: 2, threshold: 0.1 }, + ContentTier3: { limit: 3, threshold: 0.15 }, + AudienceTier3: { limit: 3, threshold: 0.2 }, + AudienceTier4: { limit: 5, threshold: 0.2 }, + AudienceTier5: { limit: 7, threshold: 0.3 }, + } : undefined; + pbjs.que.push(function () { pbjs.setConfig({ debug: true, @@ -155,10 +173,12 @@ websiteToAnalyseUrl, iabContentTaxonomyVersion, enableCache, + enableOrtb25Fields, stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, - stripFragments + stripFragments, + iabTaxonomyFilters, } } ] @@ -185,7 +205,7 @@

Basic Prebid.js Example using Neuwo Rtd Provider

- Looks like you're not following the testing environment setup, try accessing + Looks like you"re not following the testing environment setup, try accessing http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -215,12 +235,14 @@

Neuwo Rtd Provider Configuration

- +

IAB Content Taxonomy Options

- +

Cache Options

@@ -231,6 +253,14 @@

Cache Options

+

OpenRTB 2.5 Category Fields

+
+ +
+

URL Cleaning Options

- +
- +
+

IAB Taxonomy Filtering Options

+
+ +
+ When enabled, uses these hardcoded filters:
+ • ContentTier1: top 1 (≥10% relevance)
+ • ContentTier2: top 2 (≥10% relevance)
+ • ContentTier3: top 3 (≥15% relevance)
+ • AudienceTier3: top 3 (≥20% relevance)
+ • AudienceTier4: top 5 (≥20% relevance)
+ • AudienceTier5: top 7 (≥30% relevance) +
+
+ @@ -259,10 +309,10 @@

Ad Examples

Div-1

-
- Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click @@ -272,10 +322,10 @@

Div-1

Div-2

-
- Ad spot div-2: This content will be replaced by prebid.js and/or related components once you click @@ -286,82 +336,327 @@

Div-2

Neuwo Data in Bid Request

-

The retrieved data from Neuwo API is injected into the bid request as OpenRTB (ORTB2)`site.content.data` and - `user.data`. Full bid request can be inspected in Developer Tools Console under +

The retrieved data from Neuwo API is injected into the bid request as OpenRTB (ORTB2) + site.content.data and + user.data. Full bid request can be inspected in Developer Tools Console under INFO: NeuwoRTDModule injectIabCategories: post-injection bidsConfig

+

Neuwo Site Content Data

+
No data yet. Click "Update" to fetch data.
+

Neuwo User Data

+
No data yet. Click "Update" to fetch data.
+

Neuwo OpenRTB 2.5 Category Fields (IAB Content Taxonomy 1.0) Data

+
No data yet. Click "Update" to fetch data (requires enableOrtb25Fields and /v1/iab endpoint).
+
+ +
+

Accessing Neuwo Data in JavaScript

+

Listen to the bidRequested event to access the enriched ORTB2 data:

+
+pbjs.onEvent("bidRequested", function(bidRequest) {
+    const ortb2 = bidRequest.ortb2;
+    const neuwoSiteData = ortb2?.site?.content?.data?.find(d => d.name === "www.neuwo.ai");
+    const neuwoUserData = ortb2?.user?.data?.find(d => d.name === "www.neuwo.ai");
+    console.log("Neuwo data:", { siteContent: neuwoSiteData, user: neuwoUserData });
+});
+        
+

After clicking "Update", the Neuwo data is stored in the global neuwoData variable. Open + Developer Tools Console to see the logged data.

+

Note: Event timing tests for multiple Prebid.js events (auctionInit, bidRequested, + beforeBidderHttp, bidResponse, auctionEnd) are available in the page source code but are commented out. To + enable them, uncomment the timing test section in the JavaScript code.

+
+ +
+

For more information about Neuwo RTD Module configuration and accessing data retrieved from Neuwo API, see modules/neuwoRtdProvider.md.

+ +
banner
', + // no mtype + }] + }] + } + }; + const bidResponse = spec.interpretResponse(response, ortb26BidRequests)[0]; + expect(bidResponse.mediaType).to.equal('banner'); + }); + }); + }); }); describe('getUserSyncs', function () { From 734386e9c0290097d6989ff19d3f669a030dff37 Mon Sep 17 00:00:00 2001 From: cpcpn-emil <115714010+cpcpn-emil@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:14:05 +0100 Subject: [PATCH 084/105] Conceptx bid adapter: Update request destination (#14420) * New adapter: concepx * Syntax change * Revert syntax change * Defensive check for response from bidder server * Add better validation for the request * Merge branch 'master' of https://github.com/prebid/Prebid.js * Don't append url on every buildrequest * Add gvlId to conceptX * Change conceptx adapter, to directly request our PBS * Add empty getUserSync, as the syncing will be delegated (runPbsCookieSync) --------- Co-authored-by: Patrick McCann --- modules/conceptxBidAdapter.js | 279 +++++++++++++++---- test/spec/modules/conceptxBidAdapter_spec.js | 212 +++++++++----- 2 files changed, 363 insertions(+), 128 deletions(-) diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 67ebd88e4e4..eef57e0aaa8 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -1,81 +1,258 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -// import { logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; const BIDDER_CODE = 'conceptx'; -const ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; -// const LOG_PREFIX = 'ConceptX: '; +const ENDPOINT_URL = 'https://cxba-s2s.cncpt.dk/openrtb2/auction'; const GVLID = 1340; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], gvlid: GVLID, + isBidRequestValid: function (bid) { - return !!(bid.bidId && bid.params.site && bid.params.adunit); + return !!(bid.bidId && bid.params && bid.params.adunit); }, buildRequests: function (validBidRequests, bidderRequest) { - // logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); const requests = []; - let requestUrl = `${ENDPOINT_URL}` - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - requestUrl += '?gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; - requestUrl += '&consentString=' + bidderRequest.gdprConsent.consentString; - } - for (var i = 0; i < validBidRequests.length; i++) { - const requestParent = { adUnits: [], meta: {} }; - const bid = validBidRequests[i] - const { adUnitCode, auctionId, bidId, bidder, bidderRequestId, ortb2 } = bid - requestParent.meta = { adUnitCode, auctionId, bidId, bidder, bidderRequestId, ortb2 } - - const { site, adunit } = bid.params - const adUnit = { site, adunit, targetId: bid.bidId } - if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes - requestParent.adUnits.push(adUnit); + + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + const { + adUnitCode, + auctionId, + bidId, + bidder, + bidderRequestId, + ortb2 = {}, + } = bid; + const params = bid.params || {}; + + // PBS URL + GDPR query params + let url = ENDPOINT_URL; + const query = []; + + // Only add GDPR params when gdprApplies is explicitly 0 or 1 + if (bidderRequest && bidderRequest.gdprConsent) { + let gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (typeof gdprApplies === 'boolean') { + gdprApplies = gdprApplies ? 1 : 0; + } + if (gdprApplies === 0 || gdprApplies === 1) { + query.push('gdpr_applies=' + gdprApplies); + if (bidderRequest.gdprConsent.consentString) { + query.push( + 'gdpr_consent=' + + encodeURIComponent(bidderRequest.gdprConsent.consentString) + ); + } + } + } + + if (query.length) { + url += '?' + query.join('&'); + } + + // site + const page = + params.site || (ortb2.site && ortb2.site.page) || ''; + const domain = + params.domain || (ortb2.site && ortb2.site.domain) || page; + + const site = { + id: domain || page || adUnitCode, + domain: domain || '', + page: page || '', + }; + + // banner sizes from mediaTypes.banner.sizes + const formats = []; + if ( + bid.mediaTypes && + bid.mediaTypes.banner && + bid.mediaTypes.banner.sizes + ) { + let sizes = bid.mediaTypes.banner.sizes; + if (sizes.length && typeof sizes[0] === 'number') { + sizes = [sizes]; + } + for (let j = 0; j < sizes.length; j++) { + const size = sizes[j]; + if (size && size.length === 2) { + formats.push({ w: size[0], h: size[1] }); + } + } + } + + const banner = formats.length ? { format: formats } : {}; + + // currency & timeout + let currency = 'DKK'; + if ( + bidderRequest && + bidderRequest.currency && + bidderRequest.currency.adServerCurrency + ) { + currency = bidderRequest.currency.adServerCurrency; + } + + const tmax = (bidderRequest && bidderRequest.timeout) || 500; + + // device + const ua = + typeof navigator !== 'undefined' && navigator.userAgent + ? navigator.userAgent + : 'Mozilla/5.0'; + const device = { ua }; + + // build OpenRTB request for PBS with stored requests + const ortbRequest = { + id: auctionId || bidId, + site, + device, + cur: [currency], + tmax, + imp: [ + { + id: bidId, + banner, + ext: { + prebid: { + storedrequest: { + id: params.adunit, + }, + }, + }, + }, + ], + ext: { + prebid: { + storedrequest: { + id: 'cx_global', + }, + custommeta: { + adUnitCode, + auctionId, + bidId, + bidder, + bidderRequestId, + }, + }, + }, + }; + + // GDPR in body + if (bidderRequest && bidderRequest.gdprConsent) { + let gdprAppliesBody = bidderRequest.gdprConsent.gdprApplies; + if (typeof gdprAppliesBody === 'boolean') { + gdprAppliesBody = gdprAppliesBody ? 1 : 0; + } + + if (!ortbRequest.user) ortbRequest.user = {}; + if (!ortbRequest.user.ext) ortbRequest.user.ext = {}; + + if (bidderRequest.gdprConsent.consentString) { + ortbRequest.user.ext.consent = + bidderRequest.gdprConsent.consentString; + } + + if (!ortbRequest.regs) ortbRequest.regs = {}; + if (!ortbRequest.regs.ext) ortbRequest.regs.ext = {}; + + if (gdprAppliesBody === 0 || gdprAppliesBody === 1) { + ortbRequest.regs.ext.gdpr = gdprAppliesBody; + } + } + + // user IDs -> user.ext.eids + if (bid.userIdAsEids && bid.userIdAsEids.length) { + if (!ortbRequest.user) ortbRequest.user = {}; + if (!ortbRequest.user.ext) ortbRequest.user.ext = {}; + ortbRequest.user.ext.eids = bid.userIdAsEids; + } + requests.push({ method: 'POST', - url: requestUrl, + url, options: { - withCredentials: false, + withCredentials: true, }, - data: JSON.stringify(requestParent), + data: JSON.stringify(ortbRequest), }); } return requests; }, - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - const bidResponsesFromServer = serverResponse.body.bidResponses; - if (Array.isArray(bidResponsesFromServer) && bidResponsesFromServer.length === 0) { - return bidResponses - } - const firstBid = bidResponsesFromServer[0] - if (!firstBid) { - return bidResponses + interpretResponse: function (serverResponse, request) { + const body = + serverResponse && serverResponse.body ? serverResponse.body : {}; + + // PBS OpenRTB: seatbid[].bid[] + if ( + !body.seatbid || + !Array.isArray(body.seatbid) || + body.seatbid.length === 0 + ) { + return []; } - const firstSeat = firstBid.ads[0] - if (!firstSeat) { - return bidResponses + + const currency = body.cur || 'DKK'; + const bids = []; + + // recover referrer (site.page) from original request + let referrer = ''; + try { + if (request && request.data) { + const originalReq = + typeof request.data === 'string' + ? JSON.parse(request.data) + : request.data; + if (originalReq && originalReq.site && originalReq.site.page) { + referrer = originalReq.site.page; + } + } + } catch (_) {} + + for (let i = 0; i < body.seatbid.length; i++) { + const seatbid = body.seatbid[i]; + if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue; + + for (let j = 0; j < seatbid.bid.length; j++) { + const b = seatbid.bid[j]; + + if (!b || typeof b.price !== 'number' || !b.adm) continue; + + bids.push({ + requestId: b.impid || b.id, + cpm: b.price, + width: b.w, + height: b.h, + creativeId: b.crid || b.id || '', + dealId: b.dealid || b.dealId || undefined, + currency, + netRevenue: true, + ttl: 300, + referrer, + ad: b.adm, + }); + } } - const bidResponse = { - requestId: firstSeat.requestId, - cpm: firstSeat.cpm, - width: firstSeat.width, - height: firstSeat.height, - creativeId: firstSeat.creativeId, - dealId: firstSeat.dealId, - currency: firstSeat.currency, - netRevenue: true, - ttl: firstSeat.ttl, - referrer: firstSeat.referrer, - ad: firstSeat.html - }; - bidResponses.push(bidResponse); - return bidResponses; + + return bids; + }, + + /** + * Cookie sync for conceptx is handled by the enrichment script's runPbsCookieSync, + * which calls https://cxba-s2s.cncpt.dk/cookie_sync with bidders. The PBS returns + * bidder_status with usersync URLs, and the script runs iframe/image syncs. + * The adapter does not return sync URLs here since those come from the cookie_sync + * endpoint, not the auction response. + */ + getUserSyncs: function () { + return []; }, +}; -} registerBidder(spec); diff --git a/test/spec/modules/conceptxBidAdapter_spec.js b/test/spec/modules/conceptxBidAdapter_spec.js index 8e9bd2f8cc0..02780045496 100644 --- a/test/spec/modules/conceptxBidAdapter_spec.js +++ b/test/spec/modules/conceptxBidAdapter_spec.js @@ -1,70 +1,75 @@ -// import or require modules necessary for the test, e.g.: -import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { expect } from 'chai'; import { spec } from 'modules/conceptxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -// import { config } from 'src/config.js'; describe('conceptxBidAdapter', function () { - const URL = 'https://conceptx.cncpt-central.com/openrtb'; - - const ENDPOINT_URL = `${URL}`; - const ENDPOINT_URL_CONSENT = `${URL}?gdpr_applies=true&consentString=ihaveconsented`; + const ENDPOINT_URL = 'https://cxba-s2s.cncpt.dk/openrtb2/auction'; + const ENDPOINT_URL_CONSENT = + ENDPOINT_URL + '?gdpr_applies=1&gdpr_consent=ihaveconsented'; const adapter = newBidder(spec); const bidderRequests = [ { bidId: '123', bidder: 'conceptx', + adUnitCode: 'div-1', + auctionId: 'auc-1', params: { - site: 'example', - adunit: 'some-id-3' + site: 'example.com', + adunit: 'some-id-3', }, mediaTypes: { banner: { sizes: [[930, 180]], - } + }, }, - } - ] - - const singleBidRequest = { - bid: [ - { - bidId: '123', - } - ] - } + }, + ]; const serverResponse = { body: { - 'bidResponses': [ + id: 'resp-1', + cur: 'DKK', + seatbid: [ { - 'ads': [ + seat: 'conceptx', + bid: [ { - 'referrer': 'http://localhost/prebidpage_concept_bidder.html', - 'ttl': 360, - 'html': '

DUMMY

', - 'requestId': '214dfadd1f8826', - 'cpm': 46, - 'currency': 'DKK', - 'width': 930, - 'height': 180, - 'creativeId': 'FAKE-ID', - 'meta': { - 'mediaType': 'banner' - }, - 'netRevenue': true, - 'destinationUrls': { - 'destination': 'https://concept.dk' - } - } + id: 'bid-1', + impid: '123', + price: 46, + w: 930, + h: 180, + crid: 'FAKE-ID', + adm: '

DUMMY

', + }, ], - 'matchedAdCount': 1, - 'targetId': '214dfadd1f8826' - } - ] - } - } + }, + ], + }, + }; + + const requestPayload = { + data: JSON.stringify({ + id: 'auc-1', + site: { id: 'example.com', domain: 'example.com', page: 'example.com' }, + imp: [ + { + id: '123', + ext: { + prebid: { + storedrequest: { id: 'some-id-3' }, + }, + }, + }, + ], + ext: { + prebid: { + storedrequest: { id: 'cx_global' }, + }, + }, + }), + }; describe('inherited functions', function () { it('exists and is a function', function () { @@ -73,52 +78,105 @@ describe('conceptxBidAdapter', function () { }); describe('isBidRequestValid', function () { - it('should return true when required params found', function () { + it('should return true when bidId and params.adunit are present', function () { expect(spec.isBidRequestValid(bidderRequests[0])).to.equal(true); }); + + it('should return false when params.adunit is missing', function () { + expect( + spec.isBidRequestValid({ + bidId: '123', + bidder: 'conceptx', + params: { site: 'example' }, + }) + ).to.equal(false); + }); + + it('should return false when bidId is missing', function () { + expect( + spec.isBidRequestValid({ + bidder: 'conceptx', + params: { site: 'example', adunit: 'id-1' }, + }) + ).to.equal(false); + }); }); describe('buildRequests', function () { - it('Test requests', function () { - const request = spec.buildRequests(bidderRequests, {}); - expect(request.length).to.equal(1); - expect(request[0]).to.have.property('data'); - const bid = JSON.parse(request[0].data).adUnits[0] - expect(bid.site).to.equal('example'); - expect(bid.adunit).to.equal('some-id-3'); - expect(JSON.stringify(bid.dimensions)).to.equal(JSON.stringify([ - [930, 180]])); + it('should build OpenRTB request with stored requests', function () { + const requests = spec.buildRequests(bidderRequests, {}); + expect(requests).to.have.lengthOf(1); + expect(requests[0]).to.have.property('method', 'POST'); + expect(requests[0]).to.have.property('url', ENDPOINT_URL); + expect(requests[0]).to.have.property('data'); + + const payload = JSON.parse(requests[0].data); + expect(payload).to.have.property('site'); + expect(payload.site).to.have.property('id', 'example.com'); + expect(payload).to.have.property('imp'); + expect(payload.imp).to.have.lengthOf(1); + expect(payload.imp[0].ext.prebid.storedrequest).to.deep.equal({ + id: 'some-id-3', + }); + expect(payload.ext.prebid.storedrequest).to.deep.equal({ + id: 'cx_global', + }); + expect(payload.imp[0].banner.format).to.deep.equal([{ w: 930, h: 180 }]); + }); + + it('should include withCredentials in options', function () { + const requests = spec.buildRequests(bidderRequests, {}); + expect(requests[0].options).to.deep.include({ withCredentials: true }); }); }); describe('user privacy', function () { - it('should NOT send GDPR Consent data if gdprApplies equals undefined', function () { - const request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'iDoNotConsent' } }); - expect(request.length).to.equal(1); - expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL); + it('should NOT add GDPR params to URL when gdprApplies is undefined', function () { + const requests = spec.buildRequests(bidderRequests, { + gdprConsent: { gdprApplies: undefined, consentString: 'iDoNotConsent' }, + }); + expect(requests[0].url).to.equal(ENDPOINT_URL); }); - it('should send GDPR Consent data if gdprApplies', function () { - const request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'ihaveconsented' } }); - expect(request.length).to.equal(1); - expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); + + it('should add gdpr_applies and gdpr_consent to URL when GDPR applies', function () { + const requests = spec.buildRequests(bidderRequests, { + gdprConsent: { gdprApplies: true, consentString: 'ihaveconsented' }, + }); + expect(requests[0].url).to.include('gdpr_applies=1'); + expect(requests[0].url).to.include('gdpr_consent=ihaveconsented'); }); }); describe('interpretResponse', function () { - it('should return valid response when passed valid server response', function () { - const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); - const ad = serverResponse.body.bidResponses[0].ads[0] - expect(interpretedResponse).to.have.lengthOf(1); - expect(interpretedResponse[0].cpm).to.equal(ad.cpm); - expect(interpretedResponse[0].width).to.equal(Number(ad.width)); - expect(interpretedResponse[0].height).to.equal(Number(ad.height)); - expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.currency); - expect(interpretedResponse[0].netRevenue).to.equal(true); - expect(interpretedResponse[0].ad).to.equal(ad.html); - expect(interpretedResponse[0].ttl).to.equal(360); + it('should return valid bids from PBS seatbid format', function () { + const interpreted = spec.interpretResponse(serverResponse, requestPayload); + expect(interpreted).to.have.lengthOf(1); + expect(interpreted[0].requestId).to.equal('123'); + expect(interpreted[0].cpm).to.equal(46); + expect(interpreted[0].width).to.equal(930); + expect(interpreted[0].height).to.equal(180); + expect(interpreted[0].creativeId).to.equal('FAKE-ID'); + expect(interpreted[0].currency).to.equal('DKK'); + expect(interpreted[0].netRevenue).to.equal(true); + expect(interpreted[0].ad).to.equal('

DUMMY

'); + expect(interpreted[0].ttl).to.equal(300); + }); + + it('should return empty array when no seatbid', function () { + const emptyResponse = { body: { seatbid: [] } }; + expect(spec.interpretResponse(emptyResponse, {})).to.deep.equal([]); + }); + + it('should return empty array when seatbid is missing', function () { + const noSeatbid = { body: {} }; + expect(spec.interpretResponse(noSeatbid, {})).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('should return empty array (sync handled by runPbsCookieSync)', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [], {}, '', {}); + expect(syncs).to.deep.equal([]); }); }); }); From 698241b0472dc64c1f3040f2a8d54832c199051c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 26 Feb 2026 11:21:41 -0500 Subject: [PATCH 085/105] Refactor TTL usage in ttdBidAdapter (#14517) --- modules/ttdBidAdapter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 4598c3b8a7a..d985870b074 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -21,6 +21,7 @@ const BIDDER_CODE_LONG = 'thetradedesk'; const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/'; const BIDDER_ENDPOINT_HTTP2 = 'https://d2.adsrvr.org/bid/bidder/'; const USER_SYNC_ENDPOINT = 'https://match.adsrvr.org'; +const TTL = 360; const MEDIA_TYPE = { BANNER: 1, @@ -143,6 +144,8 @@ function getImpression(bidRequest) { }; const gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + const exp = TTL; + impression.exp = exp; const tagid = gpid || bidRequest.params.placementId; if (tagid) { impression.tagid = tagid; @@ -477,7 +480,7 @@ export const spec = { dealId: bid.dealid || null, currency: currency || 'USD', netRevenue: true, - ttl: bid.ttl || 360, + ttl: bid.ttl || TTL, meta: {}, }; From a00315e867c18875a0626f058b0801251e86c0dc Mon Sep 17 00:00:00 2001 From: Roger <104763658+rogerDyl@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:23:02 +0100 Subject: [PATCH 086/105] Shaping rules: Make some TypeScript fields optional (#14514) * Make TypeScript fields optional * Fix condition name --- modules/rules/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/rules/index.ts b/modules/rules/index.ts index a6667982b5e..1bbea07c5d2 100644 --- a/modules/rules/index.ts +++ b/modules/rules/index.ts @@ -55,7 +55,7 @@ interface ModelGroupSchema { /** Function name inside the schema */ function: string; /** Arguments for the schema function */ - args: any[]; + args?: any[]; } /** @@ -66,7 +66,7 @@ interface ModelGroup { /** Determines selection probability; only one object within the group is chosen */ weight: number; /** Indicates whether this model group is selected (set automatically based on weight) */ - selected: boolean; + selected?: boolean; /** Optional key used to produce aTags, identifying experiments or optimization targets */ analyticsKey: string; /** Version identifier for analytics */ @@ -82,7 +82,7 @@ interface ModelGroup { */ rules: [{ /** Conditions that must be met for the rule to apply */ - condition: string[]; + conditions: string[]; /** Resulting actions triggered when conditions are met */ results: [ { @@ -138,7 +138,7 @@ interface RulesConfig { /** One or more independent sets of rules */ ruleSets: RuleSet[]; /** Optional timestamp of the last update (ISO 8601 format: `YYYY-MM-DDThh:mm:ss[.sss][Z or ±hh:mm]`) */ - timestamp: string; + timestamp?: string; /** Enables or disables the module. Default: `true` */ enabled: boolean; } From 4d062ac6a26ffd5137502732a091bf594a3a404b Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 26 Feb 2026 11:26:40 -0500 Subject: [PATCH 087/105] OMS Adapter: extract shared OMS/Onomagic helper utilities (#14508) * OMS Adapter: extract shared OMS/Onomagic helper utilities * OMS Adapter: extract shared banner size/viewability processing * OMS Adapter: isolate viewability helpers to avoid eager side effects --- libraries/omsUtils/index.js | 27 ++++++++++++++- libraries/omsUtils/viewability.js | 19 +++++++++++ modules/omsBidAdapter.js | 52 ++++------------------------ modules/onomagicBidAdapter.js | 57 +++++-------------------------- 4 files changed, 60 insertions(+), 95 deletions(-) create mode 100644 libraries/omsUtils/viewability.js diff --git a/libraries/omsUtils/index.js b/libraries/omsUtils/index.js index b2523749080..733b257a4c8 100644 --- a/libraries/omsUtils/index.js +++ b/libraries/omsUtils/index.js @@ -1,4 +1,4 @@ -import {getWindowSelf, getWindowTop, isFn, isPlainObject} from '../../src/utils.js'; +import {createTrackPixelHtml, getWindowSelf, getWindowTop, isArray, isFn, isPlainObject} from '../../src/utils.js'; export function getBidFloor(bid) { if (!isFn(bid.getFloor)) { @@ -21,3 +21,28 @@ export function isIframe() { return true; } } + +export function getProcessedSizes(sizes = []) { + const bidSizes = ((isArray(sizes) && isArray(sizes[0])) ? sizes : [sizes]).filter(size => isArray(size)); + return bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); +} + +export function getDeviceType(ua = navigator.userAgent, 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; +} + +export function getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} diff --git a/libraries/omsUtils/viewability.js b/libraries/omsUtils/viewability.js new file mode 100644 index 00000000000..ce62f74b841 --- /dev/null +++ b/libraries/omsUtils/viewability.js @@ -0,0 +1,19 @@ +import {getWindowTop} from '../../src/utils.js'; +import {percentInView} from '../percentInView/percentInView.js'; +import {getMinSize} from '../sizeUtils/sizeUtils.js'; +import {isIframe} from './index.js'; + +export function getRoundedViewability(adUnitCode, processedSizes) { + const element = document.getElementById(adUnitCode); + const minSize = getMinSize(processedSizes); + const viewabilityAmount = isViewabilityMeasurable(element) ? getViewability(element, minSize) : 'na'; + return isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); +} + +function isViewabilityMeasurable(element) { + return !isIframe() && element !== null; +} + +function getViewability(element, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, {w, h}) : 0; +} diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 18f7c7aa12a..9eab71254d3 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -1,10 +1,7 @@ import { - isArray, - getWindowTop, deepSetValue, logError, logWarn, - createTrackPixelHtml, getBidIdParameter, getUniqueIdentifierStr, formatQS, @@ -13,10 +10,9 @@ 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 {getMinSize} from '../libraries/sizeUtils/sizeUtils.js'; -import {getBidFloor, isIframe} from '../libraries/omsUtils/index.js'; +import {getAdMarkup, getBidFloor, getDeviceType, getProcessedSizes} from '../libraries/omsUtils/index.js'; +import {getRoundedViewability} from '../libraries/omsUtils/viewability.js'; const BIDDER_CODE = 'oms'; const URL = 'https://rt.marphezis.com/hb'; @@ -39,15 +35,9 @@ export const spec = { function buildRequests(bidReqs, bidderRequest) { try { const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes || []; - bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => isArray(size)); - const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); - - const element = document.getElementById(bid.adUnitCode); - const minSize = getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; - const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + const bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes || []; + const processedSizes = getProcessedSizes(bidSizes); + const viewabilityAmountRounded = getRoundedViewability(bid.adUnitCode, processedSizes); const gpidData = _extractGpidData(bid); const imp = { @@ -101,7 +91,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 }, @@ -192,7 +182,7 @@ function interpretResponse(serverResponse) { bidResponse.vastXml = bid.adm; } else { bidResponse.mediaType = BANNER; - bidResponse.ad = _getAdMarkup(bid); + bidResponse.ad = getAdMarkup(bid); } return bidResponse; @@ -244,18 +234,6 @@ 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; @@ -266,22 +244,6 @@ 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/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index c3176d7abcc..f15ef2e63b1 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -1,17 +1,14 @@ import { _each, - createTrackPixelHtml, getBidIdParameter, + getBidIdParameter, getUniqueIdentifierStr, - getWindowTop, - isArray, logError, logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import { percentInView } from '../libraries/percentInView/percentInView.js'; -import {getMinSize} from '../libraries/sizeUtils/sizeUtils.js'; -import {getBidFloor, isIframe} from '../libraries/omsUtils/index.js'; +import {getAdMarkup, getBidFloor, getDeviceType, getProcessedSizes} from '../libraries/omsUtils/index.js'; +import {getRoundedViewability} from '../libraries/omsUtils/viewability.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -34,17 +31,9 @@ function buildRequests(bidReqs, bidderRequest) { const onomagicImps = []; const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); _each(bidReqs, function (bid) { - 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 = document.getElementById(bid.adUnitCode); - const minSize = getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, getWindowTop(), minSize) - : 'na'; - const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + const bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + const processedSizes = getProcessedSizes(bidSizes); + const viewabilityAmountRounded = getRoundedViewability(bid.adUnitCode, processedSizes); const imp = { id: bid.bidId, @@ -74,7 +63,7 @@ function buildRequests(bidReqs, bidderRequest) { } }, device: { - devicetype: _getDeviceType(), + devicetype: getDeviceType(), w: screen.width, h: screen.height }, @@ -127,7 +116,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 : [] @@ -146,34 +135,4 @@ 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); From 4fbb6e7b1ee67ee3a7098afcee0796b96d9dc131 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 26 Feb 2026 17:52:16 +0100 Subject: [PATCH 088/105] bidResponseFilter: cattax handling (#14428) * bidResponseFilter: cattax handling * default meta value * Normalize cattax values for accurate comparison Normalize cattax values before comparison to ensure accurate matching. --------- Co-authored-by: Patrick McCann --- libraries/ortbConverter/processors/default.js | 3 + modules/bidResponseFilter/index.js | 10 +- test/spec/modules/bidResponseFilter_spec.js | 122 +++++++++++++++++- 3 files changed, 126 insertions(+), 9 deletions(-) diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index b1fb5be77a5..001d0d808bf 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -111,6 +111,9 @@ export const DEFAULT_PROCESSORS = { if (bid.ext?.eventtrackers) { bidResponse.eventtrackers = (bidResponse.eventtrackers ?? []).concat(bid.ext.eventtrackers); } + if (bid.cattax) { + bidResponse.meta.cattax = bid.cattax; + } } } } diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index 64026958bc6..dc131e0bca1 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -29,7 +29,7 @@ export function reset() { } export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { - const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; + const {bcat = [], badv = [], cattax = 1} = index.getOrtb2(bid) || {}; const bidRequest = index.getBidRequest(bid); const battr = bidRequest?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; @@ -43,11 +43,15 @@ export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctio advertiserDomains = [], attr: metaAttr, mediaType: metaMediaType, + cattax: metaCattax = 1, } = bid.meta || {}; // checking if bid fulfills ortb2 fields rules - if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || - (catConfig.blockUnknown && !primaryCatId)) { + const normalizedMetaCattax = Number(metaCattax); + const normalizedRequestCattax = Number(cattax); + const isCattaxMatch = normalizedMetaCattax === normalizedRequestCattax; + if ((catConfig.enforce && isCattaxMatch && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.blockUnknown && (!isCattaxMatch || !primaryCatId))) { reject(BID_CATEGORY_REJECTION_REASON); } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || (advConfig.blockUnknown && !advertiserDomains.length)) { diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js index c37003bde50..16acee9b87e 100644 --- a/test/spec/modules/bidResponseFilter_spec.js +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -69,7 +69,8 @@ describe('bidResponseFilter', () => { advertiserDomains: ['domain1.com', 'domain2.com'], primaryCatId: 'EXAMPLE-CAT-ID', attr: 'attr', - mediaType: 'banner' + mediaType: 'banner', + cattax: 1 } }; @@ -85,7 +86,8 @@ describe('bidResponseFilter', () => { meta: { advertiserDomains: ['domain1.com', 'domain2.com'], primaryCatId: 'BANNED_CAT1', - attr: 'attr' + attr: 'attr', + cattax: 1 } }; mockAuctionIndex.getOrtb2 = () => ({ @@ -96,6 +98,109 @@ describe('bidResponseFilter', () => { sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); }); + describe('cattax (category taxonomy) match', () => { + it('should reject with BID_CATEGORY_REJECTION_REASON when cattax matches and primaryCatId is in bcat blocklist', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'BANNED_CAT1', + attr: 1, + mediaType: 'banner', + cattax: 1 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + sinon.assert.notCalled(call); + }); + + it('should pass when cattax matches and primaryCatId is not in bcat blocklist', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'ALLOWED_CAT', + attr: 1, + mediaType: 'banner', + cattax: 1 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.notCalled(reject); + sinon.assert.calledOnce(call); + }); + + it('should reject with BID_CATEGORY_REJECTION_REASON when cattax does not match (treat primaryCatId as unknown)', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'ALLOWED_CAT', + attr: 1, + mediaType: 'banner', + cattax: 2 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + sinon.assert.notCalled(call); + }); + + it('should pass when cattax does not match and blockUnknown is false (do not treat as unknown)', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com'], + primaryCatId: 'BANNED_CAT1', + attr: 1, + mediaType: 'banner', + cattax: 2 + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1'], cattax: 1 + }); + mockAuctionIndex.getBidRequest = () => ({ + mediaTypes: { banner: {} }, + ortb2Imp: {} + }); + config.setConfig({ [MODULE_NAME]: { cat: { blockUnknown: false } } }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.notCalled(reject); + sinon.assert.calledOnce(call); + }); + }); + it('should reject the bid after failed ortb2 adv domains rule validation', () => { const rejection = sinon.stub(); const call = sinon.stub(); @@ -103,7 +208,8 @@ describe('bidResponseFilter', () => { meta: { advertiserDomains: ['domain1.com', 'domain2.com'], primaryCatId: 'VALID_CAT', - attr: 'attr' + attr: 'attr', + cattax: 1 } }; mockAuctionIndex.getOrtb2 = () => ({ @@ -121,7 +227,8 @@ describe('bidResponseFilter', () => { meta: { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: 'VALID_CAT', - attr: 'BANNED_ATTR' + attr: 'BANNED_ATTR', + cattax: 1 }, mediaType: 'video' }; @@ -149,6 +256,7 @@ describe('bidResponseFilter', () => { primaryCatId: 'BANNED_CAT1', attr: 'valid_attr', mediaType: 'banner', + cattax: 1 } }; @@ -177,7 +285,8 @@ describe('bidResponseFilter', () => { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: undefined, attr: 'valid_attr', - mediaType: 'banner' + mediaType: 'banner', + cattax: 1 } }; @@ -207,7 +316,8 @@ describe('bidResponseFilter', () => { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: 'VALID_CAT', attr: 6, - mediaType: 'audio' + mediaType: 'audio', + cattax: 1 }, }; From 969a50b126723694d617aa3a98846d4889aad9f4 Mon Sep 17 00:00:00 2001 From: Anna Yablonsky Date: Thu, 26 Feb 2026 21:07:27 +0200 Subject: [PATCH 089/105] new adapter - Apester; remove alias from limelightDigital (#14516) Co-authored-by: Anna Yablonsky --- modules/apesterBidAdapter.js | 49 ++ modules/apesterBidAdapter.md | 36 + modules/limelightDigitalBidAdapter.js | 1 - test/spec/modules/apesterBidAdapter_spec.js | 775 ++++++++++++++++++++ 4 files changed, 860 insertions(+), 1 deletion(-) create mode 100644 modules/apesterBidAdapter.js create mode 100644 modules/apesterBidAdapter.md create mode 100644 test/spec/modules/apesterBidAdapter_spec.js diff --git a/modules/apesterBidAdapter.js b/modules/apesterBidAdapter.js new file mode 100644 index 00000000000..589b1b5210f --- /dev/null +++ b/modules/apesterBidAdapter.js @@ -0,0 +1,49 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { + isBidRequestValid, + onBidWon, + createUserSyncGetter, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; + +const DEFAULT_SUB_DOMAIN = 'bidder'; +const BIDDER_CODE = 'apester'; +const BIDDER_VERSION = '1.0.0'; +const GVLID = 354; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.apester.com`; +} + +function createUniqueRequestData(hashUrl, bid) { + const {auctionId, transactionId} = bid; + return { + auctionId, + transactionId + }; +} + +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.apester.com/api/sync/iframe', + imageSyncUrl: 'https://sync.apester.com/api/sync/image' +}); + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, +}; + +registerBidder(spec); diff --git a/modules/apesterBidAdapter.md b/modules/apesterBidAdapter.md new file mode 100644 index 00000000000..c43707d8a28 --- /dev/null +++ b/modules/apesterBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +**Module Name:** Apester Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** roni.katz@apester.com + +# Description + +Module that connects to Apester's demand sources. + +# Test Parameters + +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'apester', + params: { + cId: '562524b21b1c1f08117667f9', + pId: '59ac17c192832d0016683fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index b0f6423af8c..b0aea80cb01 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -74,7 +74,6 @@ export const spec = { aliases: [ { code: 'pll' }, { code: 'iionads', gvlid: 1358 }, - { code: 'apester' }, { code: 'adsyield' }, { code: 'tgm' }, { code: 'adtg_org' }, diff --git a/test/spec/modules/apesterBidAdapter_spec.js b/test/spec/modules/apesterBidAdapter_spec.js new file mode 100644 index 00000000000..25aa0d4003c --- /dev/null +++ b/test/spec/modules/apesterBidAdapter_spec.js @@ -0,0 +1,775 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + storage, +} from 'modules/apesterBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; +import {config} from '../../../src/config.js'; +import { + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from '../../../libraries/vidazooUtils/bidderUtils.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7], + 'coppa': 0 + }, + 'device': ORTB2_DEVICE, + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['bidder.apester.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const ORTB2_OBJ = { + "device": ORTB2_DEVICE, + "regs": {"coppa": 0, "gpp": "gpp_string", "gpp_sid": [7]}, + "site": {"content": {"language": "en"} + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('apesterBidAdapter', function () { + before(() => config.resetConfig()); + after(() => config.resetConfig()); + + describe('validate spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + getGlobal().bidderSettings = { + apester: { + storageAllowed: true + } + }; + sandbox = sinon.createSandbox(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2Imp: BID.ortb2Imp, + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + after(function () { + getGlobal().bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.apester.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.apester.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.apester.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', + 'type': 'image' + }]); + }); + + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.apester.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); + + it('should generate url with consent data', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'consent_string' + }; + const uspConsent = 'usp_string'; + const gppConsent = { + gppString: 'gpp_string', + applicableSections: [7] + } + + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.apester.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&coppa=1&gpp=gpp_string&gpp_sid=7', + 'type': 'image' + }]); + }); + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['bidder.apester.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['bidder.apester.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['bidder.apester.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + // testing bid.userIdAsEids handling + it("should include user ids from bid.userIdAsEids (length=1)", function() { + const bid = utils.deepClone(BID); + bid.userIdAsEids = [ + { + "source": "audigent.com", + "uids": [{"id": "fakeidi6j6dlc6e"}] + } + ] + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); + }) + it("should include user ids from bid.userIdAsEids (length=2)", function() { + const bid = utils.deepClone(BID); + bid.userIdAsEids = [ + { + "source": "audigent.com", + "uids": [{"id": "fakeidi6j6dlc6e"}] + }, + { + "source": "rwdcntrl.net", + "uids": [{"id": "fakeid6f35197d5c", "atype": 1}] + } + ] + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); + expect(requests[0].data['uid.rwdcntrl.net']).to.equal("fakeid6f35197d5c"); + }) + // testing user.ext.eid handling + it("should include user ids from user.ext.eid (length=1)", function() { + const bid = utils.deepClone(BID); + bid.user = { + ext: { + eids: [ + { + "source": "pubcid.org", + "uids": [{"id": "fakeid8888dlc6e"}] + } + ] + } + } + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); + }) + it("should include user ids from user.ext.eid (length=2)", function() { + const bid = utils.deepClone(BID); + bid.user = { + ext: { + eids: [ + { + "source": "pubcid.org", + "uids": [{"id": "fakeid8888dlc6e"}] + }, + { + "source": "adserver.org", + "uids": [{"id": "fakeid495ff1"}] + } + ] + } + } + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); + expect(requests[0].data['uid.adserver.org']).to.equal("fakeid495ff1"); + }) + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + getGlobal().bidderSettings = { + apester: { + storageAllowed: true + } + }; + }); + after(function () { + getGlobal().bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(storage, key, 0); + }) + + 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) + }); + }); + + describe('storage utils', function () { + before(function () { + getGlobal().bidderSettings = { + apester: { + storageAllowed: true + } + }; + }); + after(function () { + getGlobal().bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem(storage, 'myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From 21342e37b32a631f5ddcc5a43e7404f933988145 Mon Sep 17 00:00:00 2001 From: Anna Yablonsky Date: Thu, 26 Feb 2026 21:07:58 +0200 Subject: [PATCH 090/105] New adapter - Adnimation (#14502) * adding new adapter for Admination * remove alias for adnimation form limelightDigital following new partnership --------- Co-authored-by: Anna Yablonsky --- modules/adnimationBidAdapter.js | 47 ++ modules/adnimationBidAdapter.md | 36 + modules/limelightDigitalBidAdapter.js | 1 - .../spec/modules/adminationBidAdapter_spec.js | 775 ++++++++++++++++++ 4 files changed, 858 insertions(+), 1 deletion(-) create mode 100644 modules/adnimationBidAdapter.js create mode 100644 modules/adnimationBidAdapter.md create mode 100644 test/spec/modules/adminationBidAdapter_spec.js diff --git a/modules/adnimationBidAdapter.js b/modules/adnimationBidAdapter.js new file mode 100644 index 00000000000..21ac9090f38 --- /dev/null +++ b/modules/adnimationBidAdapter.js @@ -0,0 +1,47 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { + isBidRequestValid, + onBidWon, + createUserSyncGetter, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; + +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'adnimation'; +const BIDDER_VERSION = '1.0.0'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.adnimation.com`; +} + +function createUniqueRequestData(hashUrl, bid) { + const {auctionId, transactionId} = bid; + return { + auctionId, + transactionId + }; +} + +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.adnimation.com/api/sync/iframe', + imageSyncUrl: 'https://sync.adnimation.com/api/sync/image' +}); + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, +}; + +registerBidder(spec); diff --git a/modules/adnimationBidAdapter.md b/modules/adnimationBidAdapter.md new file mode 100644 index 00000000000..df62967bd8d --- /dev/null +++ b/modules/adnimationBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +**Module Name:** Adnimation Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** prebid@adnimation.com + +# Description + +Module that connects to Adnimation's demand sources. + +# Test Parameters + +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'adnimation', + params: { + cId: '562524b21b1c1f08117667f9', + pId: '59ac17c192832d0016683fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index b0aea80cb01..34c5704155f 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -83,7 +83,6 @@ export const spec = { { code: 'stellorMediaRtb' }, { code: 'smootai' }, { code: 'anzuExchange' }, - { code: 'adnimation' }, { code: 'rtbdemand' }, { code: 'altstar' }, { code: 'vaayaMedia' }, diff --git a/test/spec/modules/adminationBidAdapter_spec.js b/test/spec/modules/adminationBidAdapter_spec.js new file mode 100644 index 00000000000..077a6b50fed --- /dev/null +++ b/test/spec/modules/adminationBidAdapter_spec.js @@ -0,0 +1,775 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + storage, +} from 'modules/adnimationBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; +import {config} from '../../../src/config.js'; +import { + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from '../../../libraries/vidazooUtils/bidderUtils.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7], + 'coppa': 0 + }, + 'device': ORTB2_DEVICE, + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['adnimation.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const ORTB2_OBJ = { + "device": ORTB2_DEVICE, + "regs": {"coppa": 0, "gpp": "gpp_string", "gpp_sid": [7]}, + "site": {"content": {"language": "en"} + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('adnimationBidAdapter', function () { + before(() => config.resetConfig()); + after(() => config.resetConfig()); + + describe('validate spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + getGlobal().bidderSettings = { + adnimation: { + storageAllowed: true + } + }; + sandbox = sinon.createSandbox(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2Imp: BID.ortb2Imp, + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + after(function () { + getGlobal().bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.adnimation.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.adnimation.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.adnimation.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', + 'type': 'image' + }]); + }); + + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.adnimation.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); + + it('should generate url with consent data', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'consent_string' + }; + const uspConsent = 'usp_string'; + const gppConsent = { + gppString: 'gpp_string', + applicableSections: [7] + } + + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.adnimation.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&coppa=1&gpp=gpp_string&gpp_sid=7', + 'type': 'image' + }]); + }); + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['adnimation.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['adnimation.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['adnimation.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + // testing bid.userIdAsEids handling + it("should include user ids from bid.userIdAsEids (length=1)", function() { + const bid = utils.deepClone(BID); + bid.userIdAsEids = [ + { + "source": "audigent.com", + "uids": [{"id": "fakeidi6j6dlc6e"}] + } + ] + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); + }) + it("should include user ids from bid.userIdAsEids (length=2)", function() { + const bid = utils.deepClone(BID); + bid.userIdAsEids = [ + { + "source": "audigent.com", + "uids": [{"id": "fakeidi6j6dlc6e"}] + }, + { + "source": "rwdcntrl.net", + "uids": [{"id": "fakeid6f35197d5c", "atype": 1}] + } + ] + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); + expect(requests[0].data['uid.rwdcntrl.net']).to.equal("fakeid6f35197d5c"); + }) + // testing user.ext.eid handling + it("should include user ids from user.ext.eid (length=1)", function() { + const bid = utils.deepClone(BID); + bid.user = { + ext: { + eids: [ + { + "source": "pubcid.org", + "uids": [{"id": "fakeid8888dlc6e"}] + } + ] + } + } + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); + }) + it("should include user ids from user.ext.eid (length=2)", function() { + const bid = utils.deepClone(BID); + bid.user = { + ext: { + eids: [ + { + "source": "pubcid.org", + "uids": [{"id": "fakeid8888dlc6e"}] + }, + { + "source": "adserver.org", + "uids": [{"id": "fakeid495ff1"}] + } + ] + } + } + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); + expect(requests[0].data['uid.adserver.org']).to.equal("fakeid495ff1"); + }) + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + getGlobal().bidderSettings = { + adnimation: { + storageAllowed: true + } + }; + }); + after(function () { + getGlobal().bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(storage, key, 0); + }) + + 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) + }); + }); + + describe('storage utils', function () { + before(function () { + getGlobal().bidderSettings = { + adnimation: { + storageAllowed: true + } + }; + }); + after(function () { + getGlobal().bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem(storage, 'myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From 336b9bc6ba3b79d1da5e7576d100a124a411bfd8 Mon Sep 17 00:00:00 2001 From: MaksymTeqBlaze Date: Fri, 27 Feb 2026 10:57:31 +0200 Subject: [PATCH 091/105] TeqBlaze Bidder Utils: fix uspConsent string handling in getUserSyncs (#14515) * TeqBlazeSalesAgent Bid Adapter: initial release * update doc * fix for uspConsent string in getUserSyncs * fix test --------- Co-authored-by: Patrick McCann --- libraries/teqblazeUtils/bidderUtils.js | 4 ++-- test/spec/libraries/teqblazeUtils/bidderUtils_spec.js | 8 +++----- test/spec/modules/360playvidBidAdapter_spec.js | 8 +++----- test/spec/modules/acuityadsBidAdapter_spec.js | 8 +++----- test/spec/modules/adprimeBidAdapter_spec.js | 8 +++----- test/spec/modules/ads_interactiveBidAdapter_spec.js | 8 +++----- test/spec/modules/appStockSSPBidAdapter_spec.js | 8 +++----- test/spec/modules/beyondmediaBidAdapter_spec.js | 8 +++----- test/spec/modules/bidfuseBidAdapter_spec.js | 2 +- test/spec/modules/boldwinBidAdapter_spec.js | 8 +++----- test/spec/modules/colossussspBidAdapter_spec.js | 2 +- test/spec/modules/compassBidAdapter_spec.js | 8 +++----- test/spec/modules/contentexchangeBidAdapter_spec.js | 8 +++----- test/spec/modules/copper6sspBidAdapter_spec.js | 8 +++----- test/spec/modules/dpaiBidAdapter_spec.js | 8 +++----- test/spec/modules/emtvBidAdapter_spec.js | 8 +++----- test/spec/modules/iqzoneBidAdapter_spec.js | 8 +++----- test/spec/modules/kiviadsBidAdapter_spec.js | 8 +++----- test/spec/modules/krushmediaBidAdapter_spec.js | 8 +++----- test/spec/modules/lunamediahbBidAdapter_spec.js | 8 +++----- test/spec/modules/mathildeadsBidAdapter_spec.js | 8 +++----- test/spec/modules/mycodemediaBidAdapter_spec.js | 8 +++----- test/spec/modules/orakiBidAdapter_spec.js | 8 +++----- test/spec/modules/pgamsspBidAdapter_spec.js | 8 +++----- test/spec/modules/pubCircleBidAdapter_spec.js | 8 +++----- test/spec/modules/pubriseBidAdapter_spec.js | 8 +++----- test/spec/modules/qtBidAdapter_spec.js | 8 +++----- test/spec/modules/rocketlabBidAdapter_spec.js | 8 +++----- test/spec/modules/smarthubBidAdapter_spec.js | 10 ++++------ test/spec/modules/smootBidAdapter_spec.js | 8 +++----- test/spec/modules/visiblemeasuresBidAdapter_spec.js | 8 +++----- 31 files changed, 89 insertions(+), 145 deletions(-) diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index f310f2304d2..2c07c8ea288 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -231,8 +231,8 @@ export const getUserSyncs = (syncUrl) => (syncOptions, serverResponses, gdprCons } } - if (uspConsent && uspConsent.consentString) { - url += `&ccpa_consent=${uspConsent.consentString}`; + if (uspConsent) { + url += `&ccpa_consent=${uspConsent}`; } if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { diff --git a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js index 85ea1131db4..73055521549 100644 --- a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -516,7 +516,7 @@ describe('TeqBlazeBidderUtils', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -525,9 +525,7 @@ describe('TeqBlazeBidderUtils', function () { expect(syncData[0].url).to.equal(`https://${DOMAIN}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) }); it('Should return array of objects with proper sync config , include CCPA', function () { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -536,7 +534,7 @@ describe('TeqBlazeBidderUtils', function () { expect(syncData[0].url).to.equal(`https://${DOMAIN}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); it('Should return array of objects with proper sync config , include GPP', function () { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/360playvidBidAdapter_spec.js b/test/spec/modules/360playvidBidAdapter_spec.js index 393afbf4545..7718c0c2eca 100644 --- a/test/spec/modules/360playvidBidAdapter_spec.js +++ b/test/spec/modules/360playvidBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('360PlayVidBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -492,9 +492,7 @@ describe('360PlayVidBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookie.360playvid.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -503,7 +501,7 @@ describe('360PlayVidBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookie.360playvid.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index e37b6913ced..f7979c969df 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -531,7 +531,7 @@ describe('AcuityAdsBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -540,9 +540,7 @@ describe('AcuityAdsBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -551,7 +549,7 @@ describe('AcuityAdsBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index a917d85d6ab..d49281d79c6 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -436,7 +436,7 @@ describe('AdprimeBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -445,9 +445,7 @@ describe('AdprimeBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.adprime.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -456,7 +454,7 @@ describe('AdprimeBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.adprime.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js index c16f5a5a7b5..c3d27008182 100644 --- a/test/spec/modules/ads_interactiveBidAdapter_spec.js +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -482,7 +482,7 @@ describe('AdsInteractiveBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -491,9 +491,7 @@ describe('AdsInteractiveBidAdapter', function () { expect(syncData[0].url).to.equal('https://cstb.adsinteractive.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -502,7 +500,7 @@ describe('AdsInteractiveBidAdapter', function () { expect(syncData[0].url).to.equal('https://cstb.adsinteractive.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/appStockSSPBidAdapter_spec.js b/test/spec/modules/appStockSSPBidAdapter_spec.js index 7ee7d739dd3..64dac5fe8e7 100644 --- a/test/spec/modules/appStockSSPBidAdapter_spec.js +++ b/test/spec/modules/appStockSSPBidAdapter_spec.js @@ -499,7 +499,7 @@ describe('AppStockSSPBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -508,9 +508,7 @@ describe('AppStockSSPBidAdapter', function () { expect(syncData[0].url).to.equal('https://csync.al-ad.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -519,7 +517,7 @@ describe('AppStockSSPBidAdapter', function () { expect(syncData[0].url).to.equal('https://csync.al-ad.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 79bf88cb6be..5ee8d73207b 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -431,7 +431,7 @@ describe('AndBeyondMediaBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -440,9 +440,7 @@ describe('AndBeyondMediaBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -451,7 +449,7 @@ describe('AndBeyondMediaBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/bidfuseBidAdapter_spec.js b/test/spec/modules/bidfuseBidAdapter_spec.js index 95c84dcdf87..3c247c17b43 100644 --- a/test/spec/modules/bidfuseBidAdapter_spec.js +++ b/test/spec/modules/bidfuseBidAdapter_spec.js @@ -337,7 +337,7 @@ describe('BidfuseBidAdapter', function () { const result = spec.getUserSyncs({pixelEnabled: true}, [serverResponse], gdprConsent, uspConsent, gppConsent); expect(result).to.deep.equal([{ - 'url': 'https://syncbf.bidfuse.com/image?pbjs=1&gdpr=1&gdpr_consent=consent_string&gpp=gpp_string&gpp_sid=7&coppa=1', + 'url': 'https://syncbf.bidfuse.com/image?pbjs=1&gdpr=1&gdpr_consent=consent_string&ccpa_consent=usp_string&gpp=gpp_string&gpp_sid=7&coppa=1', 'type': 'image' }]); }); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 5d8c7fab9fd..fdaa0f19f3f 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -434,7 +434,7 @@ describe('BoldwinBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -443,9 +443,7 @@ describe('BoldwinBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.videowalldirect.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -454,7 +452,7 @@ describe('BoldwinBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.videowalldirect.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 9c92661fd54..cc926e61b9e 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -447,7 +447,7 @@ describe('ColossussspAdapter', function () { }) describe('getUserSyncs', function () { - const userSync = spec.getUserSyncs({}, {}, { consentString: 'xxx', gdprApplies: 1 }, { consentString: '1YN-' }); + const userSync = spec.getUserSyncs({}, {}, { consentString: 'xxx', gdprApplies: 1 }, '1YN-'); it('Returns valid URL and type', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 8d0e1cc5715..57412de4379 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -482,7 +482,7 @@ describe('CompassBidAdapter', function () { const syncData = config.runWithBidder(bidder, () => spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {})); + }, undefined)); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -491,9 +491,7 @@ describe('CompassBidAdapter', function () { expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = config.runWithBidder(bidder, () => spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - })); + const syncData = config.runWithBidder(bidder, () => spec.getUserSyncs({}, {}, {}, '1---')); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -502,7 +500,7 @@ describe('CompassBidAdapter', function () { expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = config.runWithBidder(bidder, () => spec.getUserSyncs({}, {}, {}, {}, { + const syncData = config.runWithBidder(bidder, () => spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] })); diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 12a4c6c5de2..cdf5be50250 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -481,7 +481,7 @@ describe('ContentexchangeBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -490,9 +490,7 @@ describe('ContentexchangeBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync2.adnetwork.agency/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -501,7 +499,7 @@ describe('ContentexchangeBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync2.adnetwork.agency/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js index 3c32750cbc0..888291c5997 100644 --- a/test/spec/modules/copper6sspBidAdapter_spec.js +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -484,7 +484,7 @@ describe('Copper6SSPBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -493,9 +493,7 @@ describe('Copper6SSPBidAdapter', function () { expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -504,7 +502,7 @@ describe('Copper6SSPBidAdapter', function () { expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/dpaiBidAdapter_spec.js b/test/spec/modules/dpaiBidAdapter_spec.js index f412a3316f0..f81c3aa7c95 100644 --- a/test/spec/modules/dpaiBidAdapter_spec.js +++ b/test/spec/modules/dpaiBidAdapter_spec.js @@ -478,7 +478,7 @@ describe('DpaiBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -487,9 +487,7 @@ describe('DpaiBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.drift-pixel.ai/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -498,7 +496,7 @@ describe('DpaiBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.drift-pixel.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js index 0e91f3fa719..a75522607c1 100644 --- a/test/spec/modules/emtvBidAdapter_spec.js +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -485,7 +485,7 @@ describe('EMTVBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -494,9 +494,7 @@ describe('EMTVBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -505,7 +503,7 @@ describe('EMTVBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index c14b85b2c8b..7f48b7077bf 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -480,7 +480,7 @@ describe('IQZoneBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -489,9 +489,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -500,7 +498,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index d7f9a233c9d..d7cbd189782 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('KiviAdsBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -492,9 +492,7 @@ describe('KiviAdsBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -503,7 +501,7 @@ describe('KiviAdsBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 98bdbcbb855..044d9d4f26b 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('KrushmediabBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -492,9 +492,7 @@ describe('KrushmediabBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.krushmedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -503,7 +501,7 @@ describe('KrushmediabBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.krushmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index 79c9c0d6172..a0b4a02cc57 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -432,7 +432,7 @@ describe('LunamediaHBBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -441,9 +441,7 @@ describe('LunamediaHBBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookie.lmgssp.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -452,7 +450,7 @@ describe('LunamediaHBBidAdapter', function () { expect(syncData[0].url).to.equal('https://cookie.lmgssp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 05336197872..21b7b4a7d21 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -432,7 +432,7 @@ describe('MathildeAdsBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -441,9 +441,7 @@ describe('MathildeAdsBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs2.mathilde-ads.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -452,7 +450,7 @@ describe('MathildeAdsBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs2.mathilde-ads.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/mycodemediaBidAdapter_spec.js b/test/spec/modules/mycodemediaBidAdapter_spec.js index 7cc9b412ea0..fbf40f4b403 100644 --- a/test/spec/modules/mycodemediaBidAdapter_spec.js +++ b/test/spec/modules/mycodemediaBidAdapter_spec.js @@ -478,7 +478,7 @@ describe('MyCodeMediaBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -487,9 +487,7 @@ describe('MyCodeMediaBidAdapter', function () { expect(syncData[0].url).to.equal('https://usersync.mycodemedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -498,7 +496,7 @@ describe('MyCodeMediaBidAdapter', function () { expect(syncData[0].url).to.equal('https://usersync.mycodemedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js index 4a6b8fa7d36..f3f31be3e30 100644 --- a/test/spec/modules/orakiBidAdapter_spec.js +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -478,7 +478,7 @@ describe('OrakiBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -487,9 +487,7 @@ describe('OrakiBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.oraki.io/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -498,7 +496,7 @@ describe('OrakiBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.oraki.io/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index 6eea9bec92a..b881be50402 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -481,7 +481,7 @@ describe('PGAMBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -490,9 +490,7 @@ describe('PGAMBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pgammedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -501,7 +499,7 @@ describe('PGAMBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pgammedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index 97953192a6e..ebf53063c52 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -432,7 +432,7 @@ describe('PubCircleBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -441,9 +441,7 @@ describe('PubCircleBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pubcircle.ai/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -452,7 +450,7 @@ describe('PubCircleBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.pubcircle.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js index 786f6a98b5c..37aaa964602 100644 --- a/test/spec/modules/pubriseBidAdapter_spec.js +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -482,7 +482,7 @@ describe('PubriseBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -491,9 +491,7 @@ describe('PubriseBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.pubrise.ai/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -502,7 +500,7 @@ describe('PubriseBidAdapter', function () { expect(syncData[0].url).to.equal('https://sync.pubrise.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js index 279962d0d3c..b2b7511cb18 100644 --- a/test/spec/modules/qtBidAdapter_spec.js +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -481,7 +481,7 @@ describe('QTBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -490,9 +490,7 @@ describe('QTBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.qt.io/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -501,7 +499,7 @@ describe('QTBidAdapter', function () { expect(syncData[0].url).to.equal('https://cs.qt.io/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); diff --git a/test/spec/modules/rocketlabBidAdapter_spec.js b/test/spec/modules/rocketlabBidAdapter_spec.js index fc162c67959..ffe48e4c2d9 100644 --- a/test/spec/modules/rocketlabBidAdapter_spec.js +++ b/test/spec/modules/rocketlabBidAdapter_spec.js @@ -544,7 +544,7 @@ describe("RocketLabBidAdapter", function () { consentString: "ALL", gdprApplies: true, }, - {} + undefined ); expect(syncData).to.be.an("array").which.is.not.empty; expect(syncData[0]).to.be.an("object"); @@ -560,9 +560,7 @@ describe("RocketLabBidAdapter", function () { {}, {}, {}, - { - consentString: "1---", - } + "1---" ); expect(syncData).to.be.an("array").which.is.not.empty; expect(syncData[0]).to.be.an("object"); @@ -578,7 +576,7 @@ describe("RocketLabBidAdapter", function () { {}, {}, {}, - {}, + undefined, { gppString: "abc123", applicableSections: [8], diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index 29607365c68..19848ffd03f 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -446,7 +446,7 @@ describe('SmartHubBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -455,9 +455,7 @@ describe('SmartHubBidAdapter', function () { expect(syncData[0].url).to.equal('https://us4.shb-sync.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0&pid=360') }); it('Should return array of objects with CCPA values', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -466,7 +464,7 @@ describe('SmartHubBidAdapter', function () { expect(syncData[0].url).to.equal('https://us4.shb-sync.com/image?pbjs=1&ccpa_consent=1---&coppa=0&pid=360') }); it('Should return array of objects with GPP values', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'ab12345', applicableSections: [8] }); @@ -478,7 +476,7 @@ describe('SmartHubBidAdapter', function () { expect(syncData[0].url).to.equal('https://us4.shb-sync.com/image?pbjs=1&gpp=ab12345&gpp_sid=8&coppa=0&pid=360') }); it('Should return iframe type if iframeEnabled is true', function() { - const syncData = spec.getUserSyncs({iframeEnabled: true}, {}, {}, {}, {}); + const syncData = spec.getUserSyncs({iframeEnabled: true}, {}, {}, undefined, {}); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') diff --git a/test/spec/modules/smootBidAdapter_spec.js b/test/spec/modules/smootBidAdapter_spec.js index d9c5580e60d..7ab8100bbe1 100644 --- a/test/spec/modules/smootBidAdapter_spec.js +++ b/test/spec/modules/smootBidAdapter_spec.js @@ -544,7 +544,7 @@ describe('SmootBidAdapter', function () { consentString: 'ALL', gdprApplies: true, }, - {} + undefined ); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object'); @@ -560,9 +560,7 @@ describe('SmootBidAdapter', function () { {}, {}, {}, - { - consentString: '1---', - } + '1---' ); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object'); @@ -578,7 +576,7 @@ describe('SmootBidAdapter', function () { {}, {}, {}, - {}, + undefined, { gppString: 'abc123', applicableSections: [8], diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index d17e82a1c7a..b76805e4ba0 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -483,7 +483,7 @@ describe('VisibleMeasuresBidAdapter', function () { const syncData = spec.getUserSyncs({}, {}, { consentString: 'ALL', gdprApplies: true, - }, {}); + }, undefined); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -492,9 +492,7 @@ describe('VisibleMeasuresBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) }); it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1---' - }); + const syncData = spec.getUserSyncs({}, {}, {}, '1---'); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') @@ -503,7 +501,7 @@ describe('VisibleMeasuresBidAdapter', function () { expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({}, {}, {}, {}, { + const syncData = spec.getUserSyncs({}, {}, {}, undefined, { gppString: 'abc123', applicableSections: [8] }); From 064d3a44f6534f7a0a42c4cc1f99d14e5dee158f Mon Sep 17 00:00:00 2001 From: ahzgg <163184035+ahzgg@users.noreply.github.com> Date: Fri, 27 Feb 2026 03:07:26 -0600 Subject: [PATCH 092/105] GumGum Adapter: migrate identity extraction to EIDs (#14511) * ADJS-1646-update-userId-handling * added test: should filter pubProvidedId entries by allowed sources * support ortb2.user.ext.eids fallback and add identity parity tests * Prioritize prebid.js 10 structure * GumGum Adapter: fix TDID extraction across all EID uids and add edge-case tests --- modules/gumgumBidAdapter.js | 92 +++++--- test/spec/modules/gumgumBidAdapter_spec.js | 246 +++++++++++++++++---- 2 files changed, 261 insertions(+), 77 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 3f7339a4f08..1b8cc95e2dc 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -1,12 +1,11 @@ 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'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -324,28 +323,53 @@ function getGreatestDimensions(sizes) { return [maxw, maxh]; } -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; +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; + } } - return eids; + return ids; }, {}); } @@ -369,12 +393,12 @@ function buildRequests(validBidRequests, bidderRequest) { bidId, mediaTypes = {}, params = {}, - userId = {}, ortb2Imp, adUnitCode = '' } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); - const eids = getEids(userId); + const userEids = getUserEids(bidRequest, bidderRequest); + const eids = getEidsFromEidsArray(userEids); const gpid = deepAccess(ortb2Imp, 'ext.gpid'); const paapiEligible = deepAccess(ortb2Imp, 'ext.ae') === 1 let sizes = [1, 1]; @@ -399,16 +423,20 @@ function buildRequests(validBidRequests, bidderRequest) { } } // Send filtered pubProvidedId's - if (userId && userId.pubProvidedId) { - const filteredData = userId.pubProvidedId.filter(item => pubProvidedIdSources.includes(item.source)); + if (userEids.length) { + const filteredData = userEids.filter(isPubProvidedIdEid); const maxLength = 1800; // replace this with your desired maximum length const truncatedJsonString = jsoStringifynWithMaxLength(filteredData, maxLength); - data.pubProvidedId = truncatedJsonString + if (filteredData.length) { + data.pubProvidedId = truncatedJsonString + } } // ADJS-1286 Read id5 id linktype field - if (userId && userId.id5id && userId.id5id.uid && userId.id5id.ext) { - data.id5Id = userId.id5id.uid || null - data.id5IdLinkType = userId.id5id.ext.linkType || null + 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 } // ADTS-169 add adUnitCode to requests if (adUnitCode) data.aun = adUnitCode; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 1ceaf4f2646..67caf24dba3 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -100,6 +100,39 @@ 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: { @@ -136,38 +169,7 @@ describe('gumgumAdapter', function () { sizes: sizesArray } }, - 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', - }, - ], + userIdAsEids: [id5Eid, ...pubProvidedIdEids], adUnitCode: 'adunit-code', sizes: sizesArray, bidId: '30b31c1838de1e', @@ -227,13 +229,139 @@ 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(bidRequests[0].userId.pubProvidedId)); + 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); }); 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(bidRequests[0].userId.id5id.uid); - expect(bidRequest.data.id5IdLinkType).to.equal(bidRequests[0].userId.id5id.ext.linkType); + 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'); }); it('should set pubId param if found', function () { @@ -614,7 +742,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(bidRequests[0].userId.pubProvidedId)); + expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(pubProvidedIdEids)); }); it('should add gdpr consent parameters if gdprConsent is present', function () { @@ -714,14 +842,40 @@ 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 unifiedId = { - 'userId': { - 'tdid': 'tradedesk-id' - } - } - const request = Object.assign(unifiedId, bidRequests[0]); + 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 bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.tdid).to.eq(unifiedId.userId.tdid); + expect(bidRequest.data.tdid).to.eq('tradedesk-id'); }); it('should not add a tdid parameter if unified id is not found', function () { const request = spec.buildRequests(bidRequests)[0]; @@ -729,7 +883,8 @@ describe('gumgumAdapter', function () { }); it('should send IDL envelope ID if available', function () { const idl_env = 'abc123'; - const request = { ...bidRequests[0], userId: { idl_env } }; + const idlEid = { source: 'liveramp.com', uids: [{ id: idl_env }] }; + const request = { ...bidRequests[0], userIdAsEids: [idlEid] }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('idl_env'); @@ -743,7 +898,8 @@ describe('gumgumAdapter', function () { }); it('should add a uid2 parameter if request contains uid2 id', function () { const uid2 = { id: 'sample-uid2' }; - const request = { ...bidRequests[0], userId: { uid2 } }; + const uid2Eid = { source: 'uidapi.com', uids: [{ id: uid2.id }] }; + const request = { ...bidRequests[0], userIdAsEids: [uid2Eid] }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('uid2'); From 192f96a8c3f42a24a23930b69d36b3791a76ab1e Mon Sep 17 00:00:00 2001 From: ibhattacharya-dev Date: Fri, 27 Feb 2026 14:45:30 +0530 Subject: [PATCH 093/105] mediafuseBidAdapter - Updates and Refactor (#14469) * mediafuseBidAdapter Updates and Refactor * Addressed Flagged Issues and removed ADPOD import. * More Fixes * bug fixes, code quality improvements, and expanded test coverage * Updates and Fixes * Flagged Issues Fixes --- modules/mediafuseBidAdapter.js | 1739 +++++----- modules/mediafuseBidAdapter.md | 8 +- test/spec/modules/mediafuseBidAdapter_spec.js | 2894 +++++++++-------- 3 files changed, 2425 insertions(+), 2216 deletions(-) diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 0bffb9219ce..a719bb5e729 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,487 +21,655 @@ 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 { config } from '../src/config.js'; import { getANKewyordParamFromMaps, getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {chunk} from '../libraries/chunk/chunk.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - */ - +import { convertCamelToUnderscore } from '../libraries/appnexusUtils/anUtils.js'; +import { chunk } from '../libraries/chunk/chunk.js'; 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; + } + } } - 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; - - const jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); - const newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); - - // find iframe containing script tag - const frameArray = document.getElementsByTagName('iframe'); + // Video + if (FEATURES.VIDEO && mediaType === VIDEO) { + bidResponse.ttl = 3600; + if (bid.nurl) { + bidResponse.vastImpUrl = bid.nurl; + } - // 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; + 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 (bid.nurl && extANData?.asset_url) { + const sep = bid.nurl.includes('?') ? '&' : '?'; + bidResponse.vastUrl = bid.nurl + sep + 'redir=' + encodeURIComponent(extANData.asset_url); + } + } - // first, loop on all ifames - for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { - const currentFrame = frameArray[i]; + // Native processing: viewability macro replacement and manual asset mapping + if (FEATURES.NATIVE && (bidAdType === 3 || mediaType === 'native')) { + bidResponse.mediaType = 'native'; try { - // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 - const nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; - - 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; + 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; } } - } 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; - } - } - } - } -} + // 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, + }; -function strIsMediafuseViewabilityScript(str) { - const regexMatchUrlStart = str.match(VIEWABILITY_URL_START); - const viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; + 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); + } - const regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); - const fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; + 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; + } + } + }); - return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; -} + // 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 }; + } -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; + 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 (e) { + logError('Mediafuse Native JSON parse 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; + // 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); + }); + } + }); + } - if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { - endpointUrl = URL_SIMPLE; + return bidResponse; } +}); - if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { - options.customHeaders = { - 'X-Is-Test': 1 - }; +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.reserve != null) ? bid.params.reserve : null; } - - 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 - }; - } - - return request; -} + // 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 newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { const renderer = Renderer.install({ @@ -504,7 +677,7 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { url: rtbBid.renderer_url, config: rendererOptions, loaded: false, - adUnitCode + adUnitCode, }); try { @@ -514,588 +687,308 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { } renderer.setEventHandlers({ - impression: () => logMessage('MediaFuse outstream video impression event'), - loaded: () => logMessage('MediaFuse outstream video loaded event'), + 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'; - } + logMessage('Mediafuse outstream renderer video event'); + const el = document.querySelector(`#${adUnitCode}`); + if (el) { + el.style.display = 'none'; + } + }, }); 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 +function hidedfpContainer(elementId) { + try { + const el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + if (el[0]) { + el[0].style.setProperty('display', 'none'); } - }; - - // 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 }); - } - - // 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 (rtbBid.buyer_member_id) { - bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)}); - } - - if (rtbBid.brand_id) { - bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); + } catch (e) { + logWarn('Mediafuse: hidedfpContainer error', e); } +} - 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 - }); - - 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; - } - } 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='); - - let jsTrackers = nativeAd.javascript_trackers; - - if (jsTrackers === undefined || jsTrackers === null) { - jsTrackers = jsTrackerDisarmed; - } else if (isStr(jsTrackers)) { - jsTrackers = [jsTrackers, jsTrackerDisarmed]; - } else { - jsTrackers.push(jsTrackerDisarmed); - } - - 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; - } - } - } catch (error) { - logError('Error appending tracking pixel', error); +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 bid; } -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; +function handleOutstreamRendererEvents(bid, id, eventName) { + try { + bid.renderer.handleVideoEvent({ + id, + eventName, + }); + } catch (err) { + logWarn(`Mediafuse: handleOutstreamRendererEvents error for ${eventName}`, err); } +} - if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - if (tag.sizes.length === 0) { - tag.sizes = transformSizes([1, 1]); - } +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]; + } - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = { layouts: [nativeRequest] }; + 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) + ); } - } - - 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; +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); } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); + if (!hasOmid && isArray(videoParams)) { + hasOmid = videoParams.includes(OMID_API); } + return hasOmid; +} - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } +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 (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]; - } - }); + buildRequests: function (bidRequests, bidderRequest) { + const options = { + withCredentials: true + }; - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; + if (getParameterByName('apn_test')?.toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + options.customHeaders = { 'X-Is-Test': 1 }; } - } - // 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; - } - }); - } + const requests = []; + const chunkedRequests = chunk(bidRequests, MAX_IMPS_PER_REQUEST); - 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; - } + chunkedRequests.forEach(batch => { + const data = converter.toORTB({ bidRequests: batch, bidderRequest }); - return tag; -} + let endpointUrl = ENDPOINT_URL_NORMAL; + if (!hasPurpose1Consent(bidderRequest.gdprConsent)) { + endpointUrl = ENDPOINT_URL_SIMPLE; + } -/* 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); - } - } + // 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; + } - return sizes; -} + 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 hasUserInfo(bid) { - return !!bid.params.user; -} + // 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; + } -function hasMemberId(bid) { - return !!parseInt(bid.params.member, 10); -} + requests.push({ + method: 'POST', + url: endpointUrl, + data, + bidderRequest, + options + }); + }); -function hasAppDeviceInfo(bid) { - if (bid.params) { - return !!bid.params.app - } -} + return requests; + }, -function hasAppId(bid) { - if (bid.params && bid.params.app) { - return !!bid.params.app.id - } - return !!bid.params.app -} + interpretResponse: function (serverResponse, request) { + const bids = converter.fromORTB({ + response: serverResponse.body, + request: request.data, + context: { + ortbRequest: request.data + } + }).bids; -function hasDebug(bid) { - return !!bid.debug -} + // 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); + } -function hasAdPod(bid) { - return ( - bid.mediaTypes && - bid.mediaTypes.video && - bid.mediaTypes.video.context === ADPOD - ); -} + return bids; + }, -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; -} + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + let gdprParams = ''; -/** - * 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; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } - const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = Math.max(...durationRangeSec); + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + syncs.push({ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + gdprParams + }); + } - 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); + }); - ['banner', 'video', 'native'].forEach(type => { - getAdUnitsStub.callsFake(function(...args) { - return adUnits; - }); + 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); + }); - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes[type] = {}; + 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); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + 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); + }); - expect(payload.tags[0].ad_types).to.deep.equal([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); + }); - if (type === 'banner') { - delete adUnits[0].mediaTypes; - } + 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; }); - }); - 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); + 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; + }); + + 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]); + }); + } + }); - expect(payload.tags[0].ad_types).to.not.exist; + // ------------------------------------------------------------------------- + // 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$' + }); }); - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; + 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; + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + // ------------------------------------------------------------------------- + // interpretResponse — outstream renderer + // ------------------------------------------------------------------------- + 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"}' + } + } + }] + }] + } + }; - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].renderer).to.exist; + expect(bids[0].adResponse.ad.renderer_config).to.equal('{"key":"val"}'); }); - 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'); + it('should set vastUrl from nurl+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, + nurl: 'https://notify.example.com/win', + ext: { + appnexus: { + bid_ad_type: 1, + asset_url: 'https://vast.example.com/vast.xml' + } + } + }] + }] + } + }; + + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].vastUrl).to.include('redir='); + expect(bids[0].vastUrl).to.include(encodeURIComponent('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' - } - } + // ------------------------------------------------------------------------- + // 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' } } - ); + }, req); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - expect(payload.tags[0].hb_source).to.deep.equal(1); + expect(logStub.calledOnce).to.be.true; + expect(logStub.firstCall.args[0]).to.include('===== Auction Debug ====='); + expect(logStub.firstCall.args[0]).to.not.include('

'); }); + }); - 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' + // ------------------------------------------------------------------------- + // interpretResponse — native exhaustive assets + // ------------------------------------------------------------------------- + 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' } }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.5, + adm: JSON.stringify(nativeAdm), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] } }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + 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).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 - }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + 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 } } + }] + }] + } + }; + + 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 add video property when adUnit includes a renderer', function () { - const videoData = { - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] - } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] - } + 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 } } + }] + }] } }; - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () {} + 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); + }); + + 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' } + ] } - }); + }; - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify(nativeAdm), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] + } + }; - 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 - }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 - }); + 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(/(?' } + } + } + }] + }] } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + }; - 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); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array'); + expect(trackers[0]).to.include('data-src='); }); - 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]); + 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 } } + }] + }] + } + }; - // 1 -> reserve not defined, getFloor not defined > empty - request = spec.buildRequests([bidRequest]); - payload = JSON.parse(request.data); + // Should not throw + expect(() => spec.interpretResponse(serverResponse, req)).to.not.throw(); + expect(logErrorStub.calledOnce).to.be.true; + }); + }); - expect(payload.tags[0].reserve).to.not.exist; + // ------------------------------------------------------------------------- + // 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 - // 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 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='); + }); + }); + + // ------------------------------------------------------------------------- + // 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 } } + }] + }] } - ); - - 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 + }; + + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].meta.dchain).to.deep.equal({ + ver: '1.0', + complete: 0, + nodes: [{ bsid: '77' }] }); - expect(payload.tags[0].hb_source).to.equal(1); + expect(bids[0].meta.advertiserId).to.equal(99); }); + }); - 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 - } - } - ); + // ------------------------------------------------------------------------- + // 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'); + }); + + 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'); + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + // ------------------------------------------------------------------------- + // isBidRequestValid + // ------------------------------------------------------------------------- + describe('isBidRequestValid', function () { + it('should return true for placement_id (snake_case)', function () { + expect(spec.isBidRequestValid({ params: { placement_id: 12345 } })).to.be.true; + }); - expect(payload.tags[0].use_pmt_rule).to.equal(true); + it('should return true for member + invCode', function () { + expect(spec.isBidRequestValid({ params: { member: '123', invCode: 'inv' } })).to.be.true; }); - 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 } }; + 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].gpid).to.exist.and.equal(testGpid) + it('should return false for member without invCode or inv_code', function () { + expect(spec.isBidRequestValid({ params: { member: '123' } })).to.be.false; }); + }); - 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 - }); + // ------------------------------------------------------------------------- + // 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); }); - 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 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; }); - 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 - } - ] - } - } - } - } - }); + 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); + }); + }); - 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 — 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 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'); + }); + }); + + // ------------------------------------------------------------------------- + // 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]); + }); + }); + + // ------------------------------------------------------------------------- + // 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'); }); + }); - it('should populate coppa if set in config', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + // ------------------------------------------------------------------------- + // 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'); + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + // ------------------------------------------------------------------------- + // 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'); + }); + }); - expect(payload.user.coppa).to.equal(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'); + }); + }); - config.getConfig.restore(); + // ------------------------------------------------------------------------- + // 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'); }); + }); - 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 — 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]); + }); + }); - const request = spec.buildRequests([bidRequest]); - expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + // ------------------------------------------------------------------------- + // 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); + }); + }); - config.getConfig.restore(); + // ------------------------------------------------------------------------- + // 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'); }); + }); - 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); + // ------------------------------------------------------------------------- + // 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; }); - 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' }] - }] - }); + 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; + }); - 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' + 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; + }); + }); + + // ------------------------------------------------------------------------- + // 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 }); + }); + }); - expect(payload.eids).to.deep.include({ - source: 'criteo.com', - id: 'sample-criteo-userid', + // ------------------------------------------------------------------------- + // 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: 'netid.de', - id: 'sample-netId-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: 'liveramp.com', - id: 'sample-idl-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: 'uidapi.com', - id: 'sample-uid2-value', - rti_partner: 'UID2' + // ------------------------------------------------------------------------- + // 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]); }); - }); + } + }); - 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] - } + // ------------------------------------------------------------------------- + // 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 } } + }] + }] } - }); - 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$' - }); - 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'" - } + }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['example.com']); + }); + + 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 } } + }] + }] } - }); - 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': {} - } - ] + }; + 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'] }] + } } - } - ] + }] + }] } - ] - }; - - 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' - }] - } - } + }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].ad).to.include('tracker.example.com/impression'); + }); + }); + + // ------------------------------------------------------------------------- + // interpretResponse — native jsTrackers combinations + // ------------------------------------------------------------------------- + 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 bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] }; - const result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + 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='); }); - it('should reject 0 cpm bids', function () { - const zeroCpmResponse = deepClone(response); - zeroCpmResponse.tags[0].ads[0].cpm = 0; + 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='); + }); - const bidderRequest = { - bidderCode: 'mediafuse' + 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'); + }); - const result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); - expect(result.length).to.equal(0); + 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' }] + } + }), + 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'); }); - it('should allow 0 cpm bids if allowZeroCpmBids setConfig is true', function () { - getGlobal().bidderSettings = { - mediafuse: { - allowZeroCpmBids: true + 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 zeroCpmResponse = deepClone(response); - zeroCpmResponse.tags[0].ads[0].cpm = 0; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify(adWithMacro), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] + } + }; - const bidderRequest = { - bidderCode: 'mediafuse', - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] + 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%'); + }); + }); + + // ------------------------------------------------------------------------- + // 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 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'); + }); - const result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); - expect(result.length).to.equal(1); - expect(result[0].cpm).to.equal(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('handles nobid responses', function () { - const response = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '84ab500420319d', - 'tag_id': 5976557, - 'auction_id': '297492697822162468', - 'nobid': true - }] + 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&/); + }); + }); + + // ------------------------------------------------------------------------- + // 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 } } } }; - 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': '' + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.have.lengthOf(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 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 } } + }] }] - }] + } }; - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - } + expect(() => spec.interpretResponse(serverResponse, req)).to.not.throw(); + }); + }); - 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': '' - }] - }] + // ------------------------------------------------------------------------- + // 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 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_ref).to.equal(''); + }); + }); + + // ------------------------------------------------------------------------- + // 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 [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; } + }); + }); + + // ------------------------------------------------------------------------- + // 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; + }); - 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, + 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 [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 + // ------------------------------------------------------------------------- + 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 + } } - }, - 'viewability': { - 'config': '' - } + }] }] - }] + } }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].renderer).to.exist; + }); + }); + + // ------------------------------------------------------------------------- + // 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); + }); + } + }); + + // ------------------------------------------------------------------------- + // 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; + }); + } + }); + + // ------------------------------------------------------------------------- + // 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); + }); + } + }); - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } + // ------------------------------------------------------------------------- + // 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 but not vastUrl when nurl present but asset_url 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'); + expect(bids[0].vastUrl).to.not.include('&redir='); + }); + } + }); + + // ------------------------------------------------------------------------- + // 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 2451e1e335af994848522a50fe94be369c50801f Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 27 Feb 2026 10:01:05 -0500 Subject: [PATCH 094/105] Revert "mediafuseBidAdapter - Updates and Refactor (#14469)" (#14529) This reverts commit 192f96a8c3f42a24a23930b69d36b3791a76ab1e. --- modules/mediafuseBidAdapter.js | 1767 +++++----- modules/mediafuseBidAdapter.md | 8 +- test/spec/modules/mediafuseBidAdapter_spec.js | 2894 ++++++++--------- 3 files changed, 2230 insertions(+), 2439 deletions(-) diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index a719bb5e729..0bffb9219ce 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -1,13 +1,8 @@ -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, - deepSetValue, + deepClone, + getBidRequest, getParameterByName, isArray, isArrayOfNums, @@ -21,974 +16,1086 @@ import { logMessage, logWarn } from '../src/utils.js'; -import { config } from '../src/config.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 } from '../libraries/appnexusUtils/anUtils.js'; -import { chunk } from '../libraries/chunk/chunk.js'; +import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; +import {chunk} from '../libraries/chunk/chunk.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'mediafuse'; -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 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 DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; -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 RESPONSE_MEDIA_TYPE_MAP = { - 0: BANNER, - 1: VIDEO, - 3: NATIVE +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 VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; -const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api']; -// Maps Prebid video context strings to Xandr's ext.appnexus.context integer (OpenRTB bid request) -const VIDEO_CONTEXT_MAP = { - 'instream': 1, - 'outstream': 4, - 'in-banner': 5 +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 USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const OMID_FRAMEWORK = 6; -const OMID_API = 7; +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; +const SCRIPT_TAG_START = ' 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] }; - }) - }; - } + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const tags = bidRequests.map(bidToTag); + const userObjBid = ((bidRequests) || []).find(hasUserInfo); + let userObj = {}; + if (config.getConfig('coppa') === true) { + userObj = { 'coppa': true }; } - 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); - } + if (userObjBid) { + 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].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) { - // 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; - } - } + 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]; + }); } - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); - if (gpid) { - extANData.gpid = gpid; + const appIdObjBid = ((bidRequests) || []).find(hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; } - if (FEATURES.VIDEO && imp.video) { - if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'instream') { - extANData.require_asset_url = true; - } - - 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]; - } - }); - } - - 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; - } - }); - } + let debugObj = {}; + const debugObjParams = {}; + const debugCookieName = 'apn_prebid_debug'; + const debugCookie = storage.getCookie(debugCookieName) || null; - if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') { - imp.video.placement = imp.video.placement || 4; + if (debugCookie) { + try { + debugObj = JSON.parse(debugCookie); + } catch (e) { + logError('MediaFuse Debug Auction Cookie Error:\n\n' + e); } - - const xandrVideoContext = VIDEO_CONTEXT_MAP[deepAccess(bidRequest, 'mediaTypes.video.context')]; - if (xandrVideoContext !== undefined) { - deepSetValue(imp, 'video.ext.appnexus.context', xandrVideoContext); + } else { + const debugBidRequest = ((bidRequests) || []).find(hasDebug); + if (debugBidRequest && debugBidRequest.debug) { + debugObj = debugBidRequest.debug; } } - if (bidRequest.renderer) { - extANData.custom_renderer_present = true; - } - Object.entries(OPTIONAL_PARAMS_MAP).forEach(([paramName, ortbName]) => { - if (bidderParams[paramName] !== undefined) { - extANData[ortbName] = bidderParams[paramName]; - } - }); + if (debugObj && debugObj.enabled) { + Object.keys(debugObj) + .filter(param => DEBUG_PARAMS.includes(param)) + .forEach(param => { + debugObjParams[param] = debugObj[param]; + }); + } - Object.keys(bidderParams) - .filter(param => !KNOWN_PARAMS.has(param)) - .forEach(param => { - extANData[convertCamelToUnderscore(param)] = bidderParams[param]; - }); + 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); - // 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(','); - } - } + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + schain: schain + }; - // Floor - const bidFloor = getBidFloor(bidRequest); - if (bidFloor) { - imp.bidfloor = bidFloor; - imp.bidfloorcur = 'USD'; - } else { - delete imp.bidfloor; - delete imp.bidfloorcur; + if (omidSupport) { + payload['iab_support'] = { + omidpn: 'Mediafuse', + omidpv: '$prebid.version$' + }; } - if (Object.keys(extANData).length > 0) { - deepSetValue(imp, 'ext.appnexus', extANData); + if (member > 0) { + payload.member_id = member; } - 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); - } + if (appDeviceObjBid) { + payload.device = appDeviceObj; } - - 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 }) }); - } - } - }); + if (appIdObjBid) { + payload.app = appIdObj; } - const extANData = { - prebid: true, - hb_source: 1, // 1 = client/web-originated header bidding request (Xandr source enum) - sdk: { - version: '$prebid.version$', - source: SOURCE - } - }; + const mfKeywords = config.getConfig('mediafuseAuctionKeywords'); + payload.keywords = getANKeywordParam(bidderRequest?.ortb2, mfKeywords); - 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 (config.getConfig('adpod.brandCategoryExclusion')) { + payload.brand_category_uniqueness = true; } - // 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 (debugObjParams.enabled) { + payload.debug = debugObjParams; + logInfo('MediaFuse Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); } - if (bidderRequest.bids?.some(bid => hasOmidSupport(bid))) { - extANData.iab_support = { - omidpn: 'Mediafuse', - omidpv: '$prebid.version$' + 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 }; - } - - 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); - const addtlConsent = acStr.split('.').map(id => parseInt(id, 10)).filter(id => !isNaN(id)); - if (addtlConsent.length > 0) { - deepSetValue(request, 'user.ext.addtl_consent', addtlConsent); - } + payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); } } - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; } - if (bidderRequest.gppConsent) { - deepSetValue(request, 'regs.gpp', bidderRequest.gppConsent.gppString); - deepSetValue(request, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + 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 (config.getConfig('coppa') === true) { - deepSetValue(request, 'regs.coppa', 1); + 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]; + }); } - // 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]; + 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'; } + eids.push(tmp); }); - request.user = userObj; - } + }); - // 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 (eids.length) { + payload.eids = eids; + } } - // 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); - } + if (tags[0].publisher_id) { + payload.publisher_id = tags[0].publisher_id; } + const request = formatRequest(payload, bidderRequest); return request; }, - 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 (!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; - } + + /** + * 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 (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 + 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); + } + } }); - - if (extANData.buyer_member_id) { - bidResponse.meta.dchain = { - ver: '1.0', - complete: 0, - nodes: [{ - bsid: extANData.buyer_member_id.toString() - }] - }; - } } - if (bid.adomain) { - const adomain = isArray(bid.adomain) ? bid.adomain : [bid.adomain]; - if (adomain.length > 0) { - bidResponse.meta = bidResponse.meta || {}; - bidResponse.meta.advertiserDomains = adomain; - } + 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); } - // Video - if (FEATURES.VIDEO && mediaType === VIDEO) { - bidResponse.ttl = 3600; - if (bid.nurl) { - bidResponse.vastImpUrl = bid.nurl; - } + return bids; + }, - 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 (bid.nurl && extANData?.asset_url) { - const sep = bid.nurl.includes('?') ? '&' : '?'; - bidResponse.vastUrl = bid.nurl + sep + 'redir=' + encodeURIComponent(extANData.asset_url); - } + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { + return [{ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + }]; } + }, - // 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, - }; + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (bid.native) { + reloadViewabilityScriptWithCorrectParameters(bid); + } + } +}; - 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); - } +function reloadViewabilityScriptWithCorrectParameters(bid) { + const viewJsPayload = getMediafuseViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); - 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; - } - } - }); + if (viewJsPayload) { + const prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; - // 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 }; - } + const jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); - 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]; + const newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); + + // find iframe containing script tag + const frameArray = document.getElementsByTagName('iframe'); + + // 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; + + // 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; + + 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; } } - if (bidResponse.native) { - bidResponse.native.javascriptTrackers = jsTrackers; - } - } catch (e) { - logError('Mediafuse Native mapping error', e); } - } 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); - }); + } 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; } - }); + } } - - 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 newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rtbBid.renderer_id, - url: rtbBid.renderer_url, - config: rendererOptions, - loaded: false, - adUnitCode, - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } +function strIsMediafuseViewabilityScript(str) { + const regexMatchUrlStart = str.match(VIEWABILITY_URL_START); + const viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; - 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.querySelector(`#${adUnitCode}`); - if (el) { - el.style.display = 'none'; - } - }, - }); - return renderer; -} + const regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); + const fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; -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 str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; } -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'); +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; + } } - } catch (e) { - logWarn('Mediafuse: hideSASIframe error', e); } + return viewJsPayload; } -function handleOutstreamRendererEvents(bid, id, eventName) { - try { - bid.renderer.handleVideoEvent({ - id, - eventName, +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 + }); }); - } catch (err) { - logWarn(`Mediafuse: handleOutstreamRendererEvents error for ${eventName}`, err); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: endpointUrl, + data: payloadString, + bidderRequest, + options + }; } + + return request; } -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]; - } +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + adUnitCode + }); - 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) - ); + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', 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'; } }); + return renderer; } -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); +/** + * 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 (!hasOmid && isArray(videoParams)) { - hasOmid = videoParams.includes(OMID_API); + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); } - return hasOmid; -} -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))); - }, + // 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() + }]}; - buildRequests: function (bidRequests, bidderRequest) { - const options = { - withCredentials: true - }; + return dchain; + } + if (rtbBid.buyer_member_id) { + bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)}); + } - if (getParameterByName('apn_test')?.toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { - options.customHeaders = { 'X-Is-Test': 1 }; - } + if (rtbBid.brand_id) { + bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); + } - const requests = []; - const chunkedRequests = chunk(bidRequests, MAX_IMPS_PER_REQUEST); + 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 + }); - chunkedRequests.forEach(batch => { - const data = converter.toORTB({ bidRequests: batch, 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]; + 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; + } + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; - let endpointUrl = ENDPOINT_URL_NORMAL; - if (!hasPurpose1Consent(bidderRequest.gdprConsent)) { - endpointUrl = ENDPOINT_URL_SIMPLE; - } + // 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='); - // 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; - } + let jsTrackers = nativeAd.javascript_trackers; - 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('&'); - } + if (jsTrackers === undefined || jsTrackers === null) { + jsTrackers = jsTrackerDisarmed; + } else if (isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else { + jsTrackers.push(jsTrackerDisarmed); + } - // 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; + 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; + } } + } catch (error) { + logError('Error appending tracking pixel', error); + } + } - requests.push({ - method: 'POST', - url: endpointUrl, - data, - bidderRequest, - options - }); - }); + return bid; +} - return requests; - }, +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; + } - interpretResponse: function (serverResponse, request) { - const bids = converter.fromORTB({ - response: serverResponse.body, - request: request.data, - context: { - ortbRequest: request.data - } - }).bids; + if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); + if (tag.sizes.length === 0) { + tag.sizes = transformSizes([1, 1]); + } - // 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); + if (bid.nativeParams) { + const nativeRequest = buildNativeRequest(bid.nativeParams); + tag[NATIVE] = { layouts: [nativeRequest] }; } + } - return bids; - }, + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { - const syncs = []; - let gdprParams = ''; + if (videoMediaType && context === 'adpod') { + tag.hb_source = 7; + } else { + tag.hb_source = 1; + } + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; - } - } + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { - syncs.push({ - type: 'iframe', - url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + gdprParams + 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]; + } }); + + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; } + } - 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); + // 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; } - syncs.push({ - type: 'image', - url: url - }); - } - } - return syncs; - }, + }); + } - onBidWon: function (bid) { - if (bid.native) { - reloadViewabilityScriptWithCorrectParameters(bid); + 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); +} - onBidderError: function ({ error, bidderRequest }) { - logError(`Mediafuse Bidder Error: ${error.message || error}`, bidderRequest); +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app } -}; +} -function reloadViewabilityScriptWithCorrectParameters(bid) { - const viewJsPayload = getMediafuseViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} - 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 hasDebug(bid) { + return !!bid.debug +} - // find iframe containing script tag - const frameArray = document.getElementsByTagName('iframe'); +function hasAdPod(bid) { + return ( + bid.mediaTypes && + bid.mediaTypes.video && + bid.mediaTypes.video.context === ADPOD + ); +} - // flag to modify only one script — prevents multiple scripts from pointing to the same creative - let modifiedAScript = false; +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; +} - // 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; - } - } - } +/** + * 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 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 getAdPodPlacementNumber(videoParams) { + const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; + const minAllowedDuration = Math.min(...durationRangeSec); + const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); - return str.startsWith(' ad.rtb); +} + +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); } } + + 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'); } - return viewJsPayload; } -function getViewabilityScriptUrlFromPayload(viewJsPayload) { - const indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; - const indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); - return viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); +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 parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; + } else { + return BANNER; + } +} + +/* function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({ source, id, rti_partner: rti }); + } else { + eids.push({ source, id }); + } + } + return eids; +} */ + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : null; + } + + const floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; } registerBidder(spec); diff --git a/modules/mediafuseBidAdapter.md b/modules/mediafuseBidAdapter.md index a9457c3e1c4..f9ed9835b94 100644 --- a/modules/mediafuseBidAdapter.md +++ b/modules/mediafuseBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Mediafuse Bid Adapter Module Type: Bidder Adapter -Maintainer: indrajit@oncoredigital.com +Maintainer: prebid-js@xandr.com ``` # Description @@ -77,7 +77,7 @@ var adUnits = [ placementId: 13232361, video: { skippable: true, - playback_method: 2 // 1=auto-play sound on, 2=auto-play sound off, 3=click-to-play, 4=mouse-over + playback_methods: ['auto_play_sound_off'] } } }] @@ -110,7 +110,7 @@ var adUnits = [ placementId: 13232385, video: { skippable: true, - playback_method: 2 // 1=auto-play sound on, 2=auto-play sound off, 3=click-to-play, 4=mouse-over + playback_method: 'auto_play_sound_off' } } } @@ -125,7 +125,7 @@ var adUnits = [ banner: { sizes: [[300, 250], [300,600]] } - }, + } bids: [{ bidder: 'mediafuse', params: { diff --git a/test/spec/modules/mediafuseBidAdapter_spec.js b/test/spec/modules/mediafuseBidAdapter_spec.js index 0e85b1c0b25..ff806d91f2c 100644 --- a/test/spec/modules/mediafuseBidAdapter_spec.js +++ b/test/spec/modules/mediafuseBidAdapter_spec.js @@ -1,1767 +1,1451 @@ -/** - * mediafuseBidAdapter_spec.js — extended tests - * - * Tests for mediafuseBidAdapter.js covering buildRequests, interpretResponse, - * getUserSyncs, and lifecycle callbacks. - */ - import { expect } from 'chai'; -import { spec, storage } from 'modules/mediafuseBidAdapter.js'; -import { deepClone } from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; -import * as utils from '../../../src/utils.js'; -import sinon from 'sinon'; - -// --------------------------------------------------------------------------- -// Shared fixtures -// --------------------------------------------------------------------------- -const BASE_BID = { - bidder: 'mediafuse', - adUnitCode: 'adunit-code', - bidId: 'bid-id-1', - params: { placementId: 12345 } -}; - -const BASE_BIDDER_REQUEST = { - auctionId: 'auction-1', - ortb2: { - site: { page: 'http://example.com', domain: 'example.com' }, - user: {} - }, - refererInfo: { - topmostLocation: 'http://example.com', - reachedTop: true, - numIframes: 0, - stack: ['http://example.com'] - }, - bids: [BASE_BID] -}; - -// --------------------------------------------------------------------------- -describe('mediafuseBidAdapter', function () { - let sandbox; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - }); +import { spec } from 'modules/mediafuseBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; - afterEach(function () { - sandbox.restore(); - }); +const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; - // ------------------------------------------------------------------------- - // buildRequests — endpoint selection - // ------------------------------------------------------------------------- - describe('buildRequests - endpoint selection', function () { - it('should use simple endpoint when GDPR purpose 1 consent is missing', function () { - const bidderRequest = deepClone(BASE_BIDDER_REQUEST); - bidderRequest.gdprConsent = { - gdprApplies: true, - consentString: 'test-consent', - vendorData: { purpose: { consents: { 1: false } } } - }; - const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); - expect(req.url).to.include('adnxs-simple.com'); - }); - }); +describe('MediaFuseAdapter', function () { + const adapter = newBidder(spec); - // ------------------------------------------------------------------------- - // buildRequests — GPID - // ------------------------------------------------------------------------- - describe('buildRequests - GPID', function () { - it('should map ortb2Imp.ext.gpid into imp.ext.appnexus.gpid', function () { - const bid = deepClone(BASE_BID); - bid.ortb2Imp = { ext: { gpid: '/1234/home#header' } }; - const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); - expect(req.data.imp[0].ext.appnexus.gpid).to.equal('/1234/home#header'); + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - // ------------------------------------------------------------------------- - // buildRequests — global keywords - // ------------------------------------------------------------------------- - describe('buildRequests - global keywords', function () { - it('should include mediafuseAuctionKeywords in request ext', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - 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('isBidRequestValid', function () { + const bid = { + 'bidder': 'mediafuse', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', function () { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { + 'member': '1234', + 'invCode': 'ABCD' + }; - // ------------------------------------------------------------------------- - // 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 }]); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); - 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 }]); + it('should return false when required params are not passed', function () { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); - // ------------------------------------------------------------------------- - // 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'); - }); - }); + 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 — 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'); + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); }); - 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]); + afterEach(function() { + getAdUnitsStub.restore(); }); - 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 parse out private sizes', function () { + const bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [300, 250] + } + } + ); - 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); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); }); - }); - // ------------------------------------------------------------------------- - // 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] + it('should add publisher_id in request', function() { + const bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + publisherId: '1231234' } - }; - 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); + }); + 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 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); - }); + 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' + } + }], + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + }]; - 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); - }); + ['banner', 'video', 'native'].forEach(type => { + getAdUnitsStub.callsFake(function(...args) { + return adUnits; + }); - 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; - }); + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[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; - }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - 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]); - }); - } - }); + expect(payload.tags[0].ad_types).to.deep.equal([type]); - // ------------------------------------------------------------------------- - // 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$' + if (type === 'banner') { + delete adUnits[0].mediaTypes; + } }); }); - 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 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); - // ------------------------------------------------------------------------- - // interpretResponse — outstream renderer - // ------------------------------------------------------------------------- - 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 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.not.exist; }); - it('should set vastUrl from nurl+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, - nurl: 'https://notify.example.com/win', - ext: { - appnexus: { - bid_ad_type: 1, - asset_url: 'https://vast.example.com/vast.xml' - } - } - }] - }] - } - }; - - const bids = spec.interpretResponse(serverResponse, req); - expect(bids[0].vastUrl).to.include('redir='); - expect(bids[0].vastUrl).to.include(encodeURIComponent('https://vast.example.com/vast.xml')); - }); - }); + 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 — 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' } - } - }, 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].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); }); - }); - - // ------------------------------------------------------------------------- - // interpretResponse — native exhaustive assets - // ------------------------------------------------------------------------- - 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'); + 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'); }); - 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 } } - }] - }] + 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' + } + } } - }; + ); - 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'); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); }); - 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 } } - }] - }] + 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' } }; - - 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); - }); - - 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' } - ] + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] } }; - const serverResponse = { - body: { - seatbid: [{ - bid: [{ - impid: impId, - price: 1.0, - adm: JSON.stringify(nativeAdm), - ext: { appnexus: { bid_ad_type: 3 } } - }] - }] - } - }; + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - 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(/(?' } - } - } - }] - }] + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, + params: { + placementId: '10433394', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } } }; - 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='); - }); - - 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 } } - }] - }] + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () {} } - }; - - // Should not throw - expect(() => spec.interpretResponse(serverResponse, req)).to.not.throw(); - expect(logErrorStub.calledOnce).to.be.true; - }); - }); + }); - // ------------------------------------------------------------------------- - // 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 + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); - 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([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 + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); }); - }); - // ------------------------------------------------------------------------- - // 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 } } - }] - }] + 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 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); - }); - }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - // ------------------------------------------------------------------------- - // 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'); + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + external_uid: '123', + segments: [{id: 123}, {id: 987, value: 876}] + }); }); - 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'); - }); - }); + 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]); - // ------------------------------------------------------------------------- - // isBidRequestValid - // ------------------------------------------------------------------------- - describe('isBidRequestValid', function () { - it('should return true for placement_id (snake_case)', function () { - expect(spec.isBidRequestValid({ params: { placement_id: 12345 } })).to.be.true; - }); + // 1 -> reserve not defined, getFloor not defined > empty + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); - it('should return true for member + invCode', function () { - expect(spec.isBidRequestValid({ params: { member: '123', invCode: 'inv' } })).to.be.true; - }); + expect(payload.tags[0].reserve).to.not.exist; - it('should return true for member + inv_code', function () { - expect(spec.isBidRequestValid({ params: { member: '123', inv_code: 'inv' } })).to.be.true; - }); - - it('should return false when no params', function () { - expect(spec.isBidRequestValid({})).to.be.false; + // 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 false for member without invCode or inv_code', function () { - expect(spec.isBidRequestValid({ params: { member: '123' } })).to.be.false; - }); - }); + 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 + } + } + ); - // ------------------------------------------------------------------------- - // 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].use_pmt_rule).to.equal(true); }); - 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 gpid to the request', function () { + const testGpid = '/12345/my-gpt-tag-0'; + const bidRequest = deepClone(bidRequests[0]); + bidRequest.ortb2Imp = { ext: { data: {}, gpid: testGpid } }; - // ------------------------------------------------------------------------- - // 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'); - }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - 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'); + expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) }); - }); - // ------------------------------------------------------------------------- - // 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]); + 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 — 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; + 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(',') }); - } - }); - - // ------------------------------------------------------------------------- - // 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'); - }); - }); + 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 — 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'); + 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 — 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'); - }); - }); + it('should populate coppa if set in config', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); - // ------------------------------------------------------------------------- - // 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'); - }); - }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - // ------------------------------------------------------------------------- - // 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]); - }); - }); + expect(payload.user.coppa).to.equal(true); - // ------------------------------------------------------------------------- - // 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); + config.getConfig.restore(); }); - }); - // ------------------------------------------------------------------------- - // 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'); - }); - }); + 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 — 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; - }); + const request = spec.buildRequests([bidRequest]); + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); - 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; + config.getConfig.restore(); }); - 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 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); }); - }); - // ------------------------------------------------------------------------- - // 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; + 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' }] + }] }); - const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); - expect(req.options.customHeaders).to.deep.equal({ 'X-Is-Test': 1 }); - }); - }); - // ------------------------------------------------------------------------- - // 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); + 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 — 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: 'criteo.com', + id: 'sample-criteo-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: 'netid.de', + id: 'sample-netId-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: 'liveramp.com', + id: 'sample-idl-userid' }); - } - }); - // ------------------------------------------------------------------------- - // 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']); + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); }); - 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 } } - }] - }] + 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] + } } - }; - 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'] }] - } - } - }] - }] + }); + 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$' + }); + 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'" + } } - }; - const bids = spec.interpretResponse(serverResponse, req); - expect(bids[0].ad).to.include('tracker.example.com/impression'); - }); - }); - - // ------------------------------------------------------------------------- - // interpretResponse — native jsTrackers combinations - // ------------------------------------------------------------------------- - 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: '' } - } + }); + 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': {} + } + ] } - }] - }] + } + ] } - }; - 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='); - }); - - 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: '' } - } - } - }] - }] + ] + }; + + 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' + }] + } + } } + ]; + 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://tracker1.com/t.js'); - expect(trackers[1]).to.include('data-src='); + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - 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'); - }); + it('should reject 0 cpm bids', function () { + const zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; - 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' }] - } - }), - ext: { appnexus: { bid_ad_type: 3 } } - }] - }] - } + const bidderRequest = { + bidderCode: 'mediafuse' }; - 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'); + + const result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(0); }); - 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' - }] + it('should allow 0 cpm bids if allowZeroCpmBids setConfig is true', function () { + getGlobal().bidderSettings = { + mediafuse: { + allowZeroCpmBids: true } }; - const serverResponse = { - body: { - seatbid: [{ - bid: [{ - impid: impId, - price: 1.0, - adm: JSON.stringify(adWithMacro), - ext: { appnexus: { bid_ad_type: 3 } } - }] - }] - } - }; + const zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; - 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%'); - }); - }); - - // ------------------------------------------------------------------------- - // 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 bidderRequest = { + bidderCode: 'mediafuse', + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] }; - 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 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'); + const result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0); }); - 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&/); - }); - }); - - // ------------------------------------------------------------------------- - // 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 } } } + it('handles nobid responses', function () { + const response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] }; - const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(syncs).to.have.lengthOf(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 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 } } - }] + 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': '' }] - } - }; - expect(() => spec.interpretResponse(serverResponse, req)).to.not.throw(); - }); - }); - - // ------------------------------------------------------------------------- - // 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 [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); - expect(req.data.ext.appnexus.referrer_detection.rd_ref).to.equal(''); - }); - }); - - // ------------------------------------------------------------------------- - // 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 [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 bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] } - }); - }); - // ------------------------------------------------------------------------- - // 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 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': '' + }] + }] }; - const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); - expect(req.data.ext.appnexus.referrer_detection.rd_stk).to.be.undefined; - }); - }); + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } + }] + } - // ------------------------------------------------------------------------- - // interpretResponse — renderer options from mediaTypes.video.renderer.options - // ------------------------------------------------------------------------- - 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 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': '' + } }] - } + }] }; - const bids = spec.interpretResponse(serverResponse, req); - expect(bids[0].renderer).to.exist; - }); - }); - - // ------------------------------------------------------------------------- - // 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); - }); - } - }); - - // ------------------------------------------------------------------------- - // 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; - }); - } - }); - - // ------------------------------------------------------------------------- - // 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 but not vastUrl when nurl present but asset_url 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 bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - }; - const bids = spec.interpretResponse(serverResponse, req); - expect(bids[0].vastImpUrl).to.equal('https://notify.example.com/win'); - expect(bids[0].vastUrl).to.not.include('&redir='); - }); - } - }); - - // ------------------------------------------------------------------------- - // 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(); - }); - 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 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': '' }; - // Exercises the array branch in getMediafuseViewabilityScriptFromJsTrackers. - expect(() => spec.onBidWon(bid)).to.not.throw(); - }); + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } - it('should not throw when tracker string does not match viewability pattern', function () { - const bid = { - adId: 'ad-id-3', - adUnitCode: 'adunit-code', - native: { javascriptTrackers: '' } + 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' + } + } + }] }; - expect(() => spec.onBidWon(bid)).to.not.throw(); - }); - it('should handle cdn.adnxs-simple.com pattern tracker', function () { - const jsScript = ''; - const bid = { - adId: 'ad-id-4', - adUnitCode: 'adunit-code', - native: { javascriptTrackers: jsScript } + 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, }; - expect(() => spec.onBidWon(bid)).to.not.throw(); - }); - }); - // ------------------------------------------------------------------------- - // 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 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', + 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); }); - }); - // ------------------------------------------------------------------------- - // 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 advertiser id', function() { + const responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_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: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); }); - 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 brand id', function() { + const responseBrandId = deepClone(response); + responseBrandId.tags[0].ads[0].brand_id = 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]); - }); - - 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; + 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 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; - }); - }); + it('should add advertiserDomains', function() { + const responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].adomain = ['123']; - // ------------------------------------------------------------------------- - // 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'); + 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([]); }); }); - - // ------------------------------------------------------------------------- - // 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 d8fb4350befed2c368da2f0f10260245d7b94a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Fri, 27 Feb 2026 16:47:10 +0100 Subject: [PATCH 095/105] Performax adapter: Add user sync and reporting URLs (#14429) 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 --------- Co-authored-by: Michal Kváček --- modules/performaxBidAdapter.js | 155 +++++++- test/spec/modules/performaxBidAdapter_spec.js | 331 +++++++++++++++++- 2 files changed, 484 insertions(+), 2 deletions(-) diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js index 48dd4366f1d..4cb72d75402 100644 --- a/modules/performaxBidAdapter.js +++ b/modules/performaxBidAdapter.js @@ -1,12 +1,94 @@ -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 { + return JSON.parse(rawData) || {}; + } 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 +122,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 +170,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 218f9402e75..5d072705d0f 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 = [{ @@ -120,6 +123,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); @@ -154,6 +207,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; @@ -172,4 +230,275 @@ 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'); + + 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 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(); + 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 2ba56bdc1129922cffc7bb2b2c10d2f13d2fa888 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 27 Feb 2026 11:50:42 -0500 Subject: [PATCH 096/105] Revert "Performax adapter: Add user sync and reporting URLs (#14429)" (#14532) This reverts commit d8fb4350befed2c368da2f0f10260245d7b94a73. --- modules/performaxBidAdapter.js | 155 +------- test/spec/modules/performaxBidAdapter_spec.js | 331 +----------------- 2 files changed, 2 insertions(+), 484 deletions(-) diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js index 4cb72d75402..48dd4366f1d 100644 --- a/modules/performaxBidAdapter.js +++ b/modules/performaxBidAdapter.js @@ -1,94 +1,12 @@ -import {logWarn, logError, deepSetValue, deepAccess, safeJSONEncode, debugTurnedOn} from '../src/utils.js'; +import { deepSetValue, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -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 { - return JSON.parse(rawData) || {}; - } 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) { @@ -122,23 +40,6 @@ 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, @@ -170,61 +71,7 @@ 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 5d072705d0f..218f9402e75 100644 --- a/test/spec/modules/performaxBidAdapter_spec.js +++ b/test/spec/modules/performaxBidAdapter_spec.js @@ -1,8 +1,5 @@ import { expect } from 'chai'; -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'; +import { spec, converter } from 'modules/performaxBidAdapter.js'; describe('Performax adapter', function () { const bids = [{ @@ -123,56 +120,6 @@ 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); @@ -207,11 +154,6 @@ 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; @@ -230,275 +172,4 @@ 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'); - - 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 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(); - 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 865eb931b9c415ccf59c7fad32ca4b3a8338c013 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:58:40 -0500 Subject: [PATCH 097/105] Bump browserstack-local from 1.5.5 to 1.5.11 (#14533) Bumps [browserstack-local](https://github.com/browserstack/browserstack-local-nodejs) from 1.5.5 to 1.5.11. - [Release notes](https://github.com/browserstack/browserstack-local-nodejs/releases) - [Commits](https://github.com/browserstack/browserstack-local-nodejs/compare/v1.5.5...v1.5.11) --- updated-dependencies: - dependency-name: browserstack-local dependency-version: 1.5.11 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 217 +++++----------------------------------------- 1 file changed, 23 insertions(+), 194 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8da1c6e41dc..4dfa1c8e28d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7715,15 +7715,15 @@ } }, "node_modules/browserstack-local": { - "version": "1.5.5", + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.11.tgz", + "integrity": "sha512-RNq0yrezPq7BXXxl/cvsbORfswUQi744po6ECkTEC2RkqNbdPyzewdy4VR9k4QHSzPHTkZx8PeH08veRtfFI8A==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^6.0.2", "https-proxy-agent": "^5.0.1", "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "tree-kill": "^1.2.2" } }, "node_modules/browserstack/node_modules/agent-base": { @@ -11109,24 +11109,6 @@ "es5-ext": "~0.10.14" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/event-stream/node_modules/map-stream": { - "version": "0.1.0", - "dev": true - }, "node_modules/event-target-shim": { "version": "5.0.1", "dev": true, @@ -11878,11 +11860,6 @@ "node": ">= 0.6" } }, - "node_modules/from": { - "version": "0.1.7", - "dev": true, - "license": "MIT" - }, "node_modules/fs-extra": { "version": "11.3.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", @@ -17650,17 +17627,6 @@ "node": "*" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "dev": true, - "license": [ - "MIT", - "Apache2" - ], - "dependencies": { - "through": "~2.3" - } - }, "node_modules/pend": { "version": "1.2.0", "dev": true, @@ -17936,20 +17902,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ps-tree": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/pump": { "version": "3.0.0", "dev": true, @@ -19462,17 +19414,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/split": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/split2": { "version": "4.2.0", "dev": true, @@ -19547,14 +19488,6 @@ "node": ">= 0.10.0" } }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexer": "~0.1.1" - } - }, "node_modules/stream-composer": { "version": "1.0.2", "dev": true, @@ -20004,47 +19937,6 @@ "streamx": "^2.12.5" } }, - "node_modules/temp-fs": { - "version": "0.9.9", - "dev": true, - "license": "MIT", - "dependencies": { - "rimraf": "~2.5.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/temp-fs/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/temp-fs/node_modules/rimraf": { - "version": "2.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/ternary-stream": { "version": "3.0.0", "dev": true, @@ -20354,6 +20246,15 @@ "node": ">=6" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "dev": true, @@ -27300,14 +27201,15 @@ } }, "browserstack-local": { - "version": "1.5.5", + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.11.tgz", + "integrity": "sha512-RNq0yrezPq7BXXxl/cvsbORfswUQi744po6ECkTEC2RkqNbdPyzewdy4VR9k4QHSzPHTkZx8PeH08veRtfFI8A==", "dev": true, "requires": { "agent-base": "^6.0.2", "https-proxy-agent": "^5.0.1", "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "tree-kill": "^1.2.2" } }, "buffer-crc32": { @@ -29478,25 +29380,6 @@ "es5-ext": "~0.10.14" } }, - "event-stream": { - "version": "3.3.4", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - }, - "dependencies": { - "map-stream": { - "version": "0.1.0", - "dev": true - } - } - }, "event-target-shim": { "version": "5.0.1", "dev": true @@ -29992,10 +29875,6 @@ "fresh": { "version": "0.5.2" }, - "from": { - "version": "0.1.7", - "dev": true - }, "fs-extra": { "version": "11.3.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", @@ -33723,13 +33602,6 @@ "version": "1.1.1", "dev": true }, - "pause-stream": { - "version": "0.0.11", - "dev": true, - "requires": { - "through": "~2.3" - } - }, "pend": { "version": "1.2.0", "dev": true @@ -33904,13 +33776,6 @@ "version": "1.0.1", "dev": true }, - "ps-tree": { - "version": "1.2.0", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, "pump": { "version": "3.0.0", "dev": true, @@ -34899,13 +34764,6 @@ "version": "3.0.18", "dev": true }, - "split": { - "version": "0.3.3", - "dev": true, - "requires": { - "through": "2" - } - }, "split2": { "version": "4.2.0", "dev": true @@ -34952,13 +34810,6 @@ "version": "3.0.2", "dev": true }, - "stream-combiner": { - "version": "0.0.4", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, "stream-composer": { "version": "1.0.2", "dev": true, @@ -35263,34 +35114,6 @@ "streamx": "^2.12.5" } }, - "temp-fs": { - "version": "0.9.9", - "dev": true, - "requires": { - "rimraf": "~2.5.2" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.5.4", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } - } - }, "ternary-stream": { "version": "3.0.0", "dev": true, @@ -35494,6 +35317,12 @@ "version": "3.0.1", "dev": true }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, "triple-beam": { "version": "1.4.1", "dev": true From 2a6e78c2c50ca42486201d3ec5b0827fadc46788 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:58:57 -0500 Subject: [PATCH 098/105] Bump fast-xml-parser from 5.3.6 to 5.4.1 (#14534) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 5.3.6 to 5.4.1. - [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.3.6...v5.4.1) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.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 | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4dfa1c8e28d..83ad693982d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11441,10 +11441,22 @@ } ] }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", "dev": true, "funding": [ { @@ -11453,6 +11465,7 @@ } ], "dependencies": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { @@ -29611,12 +29624,19 @@ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", "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 + }, "fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", "dev": true, "requires": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" } }, From cd2c4c71a306d0bbb47025ae91e4c8206457249a Mon Sep 17 00:00:00 2001 From: Guillaume Polaert Date: Fri, 27 Feb 2026 21:21:22 +0100 Subject: [PATCH 099/105] pubstackBidAdapter: initial release (#14490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add pubstackBidAdapter (#1) * feat: add SparkBidAdapter * fix: reviews * fix: use iframe for user_sync * fix: change adapter name * fix: add test file * feat: add viewport value in imp[].ext.prebid.bidder.pubstack.vpl * fix: remove unused context * Pubstack Adapter: update utils and align adapter tests * Pubstack Bid Adapter: apply lint-driven TypeScript cleanups --------- Co-authored-by: gpolaert * Update modules/pubstackBidAdapter.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/pubstackBidAdapter.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update libraries/pubstackUtils/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/pubstackBidAdapter.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Pubstack Adapter: use placementPositionInfo telemetry --------- Co-authored-by: Stéphane Deluce Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- metadata/modules.json | 7 + modules/pubstackBidAdapter.md | 30 ++ modules/pubstackBidAdapter.ts | 156 +++++++++ test/spec/modules/pubstackBidAdapter_spec.js | 348 +++++++++++++++++++ 4 files changed, 541 insertions(+) create mode 100644 modules/pubstackBidAdapter.md create mode 100644 modules/pubstackBidAdapter.ts create mode 100644 test/spec/modules/pubstackBidAdapter_spec.js diff --git a/metadata/modules.json b/metadata/modules.json index ed326efe85d..39bb38b1059 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -4488,6 +4488,13 @@ "gvlid": 104, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "pubstack", + "aliasOf": null, + "gvlid": 1408, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "sovrn", diff --git a/modules/pubstackBidAdapter.md b/modules/pubstackBidAdapter.md new file mode 100644 index 00000000000..dc3df3b8ee5 --- /dev/null +++ b/modules/pubstackBidAdapter.md @@ -0,0 +1,30 @@ +# Overview +``` +Module Name: Pubstack Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@pubstack.io +``` + +# Description +Connects to Pubstack exchange for bids. + +Pubstack bid adapter supports all media type including video, banner and native. + +# Test Parameters +``` +var adUnits = [{ + code: 'adunit-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'pubstack', + params: { + siteId: 'your-site-id', + adUnitName: 'adunit-1' + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/pubstackBidAdapter.ts b/modules/pubstackBidAdapter.ts new file mode 100644 index 00000000000..62126136e72 --- /dev/null +++ b/modules/pubstackBidAdapter.ts @@ -0,0 +1,156 @@ +import { canAccessWindowTop, deepSetValue, getWindowSelf, getWindowTop, logError } from '../src/utils.js'; +import { AdapterRequest, BidderSpec, registerBidder, ServerResponse } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { getPlacementPositionUtils } from '../libraries/placementPositionInfo/placementPositionInfo.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBRequest } from '../src/prebid.public.js'; +import { config } from '../src/config.js'; +import { SyncType } from '../src/userSync.js'; +import { ConsentData, CONSENT_GDPR, CONSENT_USP, CONSENT_GPP } from '../src/consentHandler.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const BIDDER_CODE = 'pubstack'; +const GVLID = 1408; +const REQUEST_URL = 'https://node.pbstck.com/openrtb2/auction'; +const COOKIESYNC_IFRAME_URL = 'https://cdn.pbstck.com/async_usersync.html'; +const COOKIESYNC_PIXEL_URL = 'https://cdn.pbstck.com/async_usersync.png'; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: { + siteId: string; + adUnitName: string; + }; + } +} + +type GetUserSyncFn = ( + syncOptions: { + iframeEnabled: boolean; + pixelEnabled: boolean; + }, + responses: ServerResponse[], + gdprConsent: null | ConsentData[typeof CONSENT_GDPR], + uspConsent: null | ConsentData[typeof CONSENT_USP], + gppConsent: null | ConsentData[typeof CONSENT_GPP]) => ({ type: SyncType, url: string })[] + +const siteIds: Set = new Set(); +let cntRequest = 0; +let cntTimeouts = 0; +const { getPlacementEnv, getPlacementInfo } = getPlacementPositionUtils(); + +const getElementForAdUnitCode = (adUnitCode: string): HTMLElement | undefined => { + if (!adUnitCode) return; + const win = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); + const doc = win?.document; + let element = doc?.getElementById(adUnitCode) as HTMLElement | null; + if (element) return element; + const divId = getGptSlotInfoForAdUnitCode(adUnitCode)?.divId; + element = divId ? doc?.getElementById(divId) as HTMLElement | null : null; + if (element) return element; +}; + +const converter = ortbConverter({ + imp(buildImp, bidRequest: BidRequest, context) { + const element = getElementForAdUnitCode(bidRequest.adUnitCode); + const placementInfo = getPlacementInfo(bidRequest); + const imp = buildImp(bidRequest, context); + deepSetValue(imp, `ext.prebid.bidder.${BIDDER_CODE}.adUnitName`, bidRequest.params.adUnitName); + deepSetValue(imp, `ext.prebid.placement.code`, bidRequest.adUnitCode); + deepSetValue(imp, `ext.prebid.placement.domId`, element?.id); + deepSetValue(imp, `ext.prebid.placement.viewability`, placementInfo.PlacementPercentView); + deepSetValue(imp, `ext.prebid.placement.viewportDistance`, placementInfo.DistanceToView); + deepSetValue(imp, `ext.prebid.placement.height`, placementInfo.ElementHeight); + deepSetValue(imp, `ext.prebid.placement.auctionsCount`, placementInfo.AuctionsCount); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + cntRequest++; + const placementEnv = getPlacementEnv(); + const request = buildRequest(imps, bidderRequest, context) + const siteId = bidderRequest.bids[0].params.siteId + siteIds.add(siteId); + deepSetValue(request, 'site.publisher.id', siteId); + deepSetValue(request, 'test', config.getConfig('debug') ? 1 : 0); + deepSetValue(request, 'ext.prebid.version', getGlobal()?.version ?? 'unknown'); + deepSetValue(request, `ext.prebid.request.count`, cntRequest); + deepSetValue(request, `ext.prebid.request.timeoutCount`, cntTimeouts); + deepSetValue(request, `ext.prebid.page.tabActive`, placementEnv.TabActive); + deepSetValue(request, `ext.prebid.page.height`, placementEnv.PageHeight); + deepSetValue(request, `ext.prebid.page.viewportHeight`, placementEnv.ViewportHeight); + deepSetValue(request, `ext.prebid.page.timeFromNavigation`, placementEnv.TimeFromNavigation); + return request; + }, +}); + +const isBidRequestValid = (bid: BidRequest): boolean => { + if (!bid.params.siteId || typeof bid.params.siteId !== 'string') { + logError('bid.params.siteId needs to be a string'); + if (config.getConfig('debug') === false) return false; + } + if (!bid.params.adUnitName || typeof bid.params.adUnitName !== 'string') { + logError('bid.params.adUnitName needs to be a string'); + if (config.getConfig('debug') === false) return false; + } + return true; +}; + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data: ORTBRequest = converter.toORTB({ bidRequests, bidderRequest }); + const siteId = data.site.publisher.id; + return { + method: 'POST', + url: `${REQUEST_URL}?siteId=${siteId}`, + data, + }; +}; + +const interpretResponse = (serverResponse, bidRequest) => { + if (!serverResponse?.body) { + return []; + } + return converter.fromORTB({ request: bidRequest.data, response: serverResponse.body }); +}; + +const getUserSyncs: GetUserSyncFn = (syncOptions, _serverResponses, gdprConsent, uspConsent, gppConsent) => { + const isIframeEnabled = syncOptions.iframeEnabled; + const isPixelEnabled = syncOptions.pixelEnabled; + + if (!isIframeEnabled && !isPixelEnabled) { + return []; + } + + const payload = btoa(JSON.stringify({ + gdprConsentString: gdprConsent?.consentString, + gdprApplies: gdprConsent?.gdprApplies, + uspConsent, + gpp: gppConsent?.gppString, + gpp_sid: gppConsent?.applicableSections + + })); + const syncUrl = isIframeEnabled ? COOKIESYNC_IFRAME_URL : COOKIESYNC_PIXEL_URL; + + return Array.from(siteIds).map(siteId => ({ + type: isIframeEnabled ? 'iframe' : 'image', + url: `${syncUrl}?consent=${payload}&siteId=${siteId}`, + })); +}; + +export const spec: BidderSpec = { + code: BIDDER_CODE, + aliases: [{code: `${BIDDER_CODE}_server`, gvlid: GVLID}], + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onTimeout: () => cntTimeouts++, +}; + +registerBidder(spec); diff --git a/test/spec/modules/pubstackBidAdapter_spec.js b/test/spec/modules/pubstackBidAdapter_spec.js new file mode 100644 index 00000000000..1f8143e47fa --- /dev/null +++ b/test/spec/modules/pubstackBidAdapter_spec.js @@ -0,0 +1,348 @@ +import { expect } from 'chai'; +import { spec } from 'modules/pubstackBidAdapter'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import { hook } from 'src/hook.js'; +import 'src/prebid.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; + +describe('pubstackBidAdapter', function () { + const baseBidRequest = { + adUnitCode: 'adunit-code', + auctionId: 'auction-1', + bidId: 'bid-1', + bidder: 'pubstack', + bidderRequestId: 'request-1', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + siteId: 'site-123', + adUnitName: 'adunit-1' + }, + sizes: [[300, 250]], + transactionId: 'transaction-1' + }; + + const baseBidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consent-string', + vendorData: { + purpose: { + consents: { 1: true } + } + } + }, + uspConsent: '1YYN', + gppConsent: { + gppString: 'gpp-string', + applicableSections: [7, 8] + }, + refererInfo: { + referer: 'https://example.com' + } + }; + + const clone = (obj) => JSON.parse(JSON.stringify(obj)); + + const createBidRequest = (overrides = {}) => { + const bidRequest = clone(baseBidRequest); + const { params = {}, ...otherOverrides } = overrides; + Object.assign(bidRequest, otherOverrides); + bidRequest.params = { + ...bidRequest.params, + ...params + }; + return bidRequest; + }; + + const createBidderRequest = (bidRequest, overrides = {}) => ({ + ...clone(baseBidderRequest), + bids: [bidRequest], + ...overrides + }); + + const extractBids = (result) => Array.isArray(result) ? result : result?.bids; + + const findSyncForSite = (syncs, siteId) => + syncs.find((sync) => new URL(sync.url).searchParams.get('siteId') === siteId); + + const getDecodedSyncPayload = (sync) => + JSON.parse(atob(new URL(sync.url).searchParams.get('consent'))); + + before(() => { + hook.ready(); + }); + + beforeEach(function () { + config.resetConfig(); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('isBidRequestValid', function () { + it('returns true when required params are present', function () { + expect(spec.isBidRequestValid(createBidRequest())).to.equal(true); + }); + + it('returns false for invalid params when debug is disabled', function () { + config.setConfig({ debug: false }); + expect(spec.isBidRequestValid(createBidRequest({ params: { siteId: undefined } }))).to.equal(false); + expect(spec.isBidRequestValid(createBidRequest({ params: { adUnitName: undefined } }))).to.equal(false); + }); + + it('returns true for invalid params when debug is enabled', function () { + config.setConfig({ debug: true }); + expect(spec.isBidRequestValid(createBidRequest({ params: { siteId: undefined } }))).to.equal(true); + expect(spec.isBidRequestValid(createBidRequest({ params: { adUnitName: undefined } }))).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('builds a POST request with ORTB data and bidder extensions', function () { + const bidRequest = createBidRequest(); + const bidderRequest = createBidderRequest(bidRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://node.pbstck.com/openrtb2/auction?siteId=site-123'); + expect(utils.deepAccess(request, 'data.site.publisher.id')).to.equal('site-123'); + expect(utils.deepAccess(request, 'data.test')).to.equal(0); + expect(request.data.imp).to.have.lengthOf(1); + expect(utils.deepAccess(request, 'data.imp.0.id')).to.equal('bid-1'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.bidder.pubstack.adUnitName')).to.equal('adunit-1'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.code')).to.equal('adunit-code'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.viewability')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.viewportDistance')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.imp.0.ext.prebid.placement.height')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.version')).to.be.a('string'); + expect(utils.deepAccess(request, 'data.ext.prebid.request.count')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.request.timeoutCount')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.tabActive')).to.be.a('boolean'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.height')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.viewportHeight')).to.be.a('number'); + expect(utils.deepAccess(request, 'data.ext.prebid.page.timeFromNavigation')).to.be.a('number'); + }); + + it('sets test to 1 when prebid debug mode is enabled', function () { + config.setConfig({ debug: true }); + const bidRequest = createBidRequest({ bidId: 'bid-debug' }); + const bidderRequest = createBidderRequest(bidRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); + + expect(utils.deepAccess(request, 'data.test')).to.equal(1); + }); + + it('increments request counter for each call', function () { + const firstBidRequest = createBidRequest({ bidId: 'bid-counter-1' }); + const firstRequest = spec.buildRequests([firstBidRequest], createBidderRequest(firstBidRequest)); + const secondBidRequest = createBidRequest({ + bidId: 'bid-counter-2', + adUnitCode: 'adunit-code-2', + params: { adUnitName: 'adunit-2' } + }); + const secondRequest = spec.buildRequests([secondBidRequest], createBidderRequest(secondBidRequest)); + + expect(utils.deepAccess(secondRequest, 'data.ext.prebid.request.count')) + .to.equal(utils.deepAccess(firstRequest, 'data.ext.prebid.request.count') + 1); + }); + + it('updates timeout count after onTimeout callback', function () { + const bidRequest = createBidRequest({ bidId: 'bid-timeout-rate-1' }); + const firstRequest = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + expect(utils.deepAccess(firstRequest, 'data.ext.prebid.request.timeoutCount')).to.equal(0); + + spec.onTimeout([]); + + const secondBidRequest = createBidRequest({ bidId: 'bid-timeout-rate-2' }); + const secondRequest = spec.buildRequests([secondBidRequest], createBidderRequest(secondBidRequest)); + expect(utils.deepAccess(secondRequest, 'data.ext.prebid.request.timeoutCount')).to.equal(1); + }); + }); + + describe('interpretResponse', function () { + it('returns empty array when response has no body', function () { + const bidRequest = createBidRequest(); + const request = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + const bids = spec.interpretResponse({ body: null }, request); + expect(bids).to.be.an('array'); + expect(bids).to.have.lengthOf(0); + }); + + it('maps ORTB bid responses into prebid bids', function () { + const bidRequest = createBidRequest(); + const request = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + const serverResponse = { + body: { + id: 'resp-1', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-1', + mtype: 1, + price: 1.23, + w: 300, + h: 250, + adm: '
ad
', + crid: 'creative-1' + } + ] + } + ] + } + }; + + const result = spec.interpretResponse(serverResponse, request); + const bids = extractBids(result); + expect(bids).to.have.lengthOf(1); + expect(bids[0]).to.include({ + requestId: 'bid-1', + cpm: 1.23, + width: 300, + height: 250, + ad: '
ad
', + creativeId: 'creative-1' + }); + expect(bids[0]).to.have.property('currency', 'USD'); + }); + + it('returns no bids when ORTB response impid does not match request imp ids', function () { + const bidRequest = createBidRequest({ bidId: 'bid-match-required' }); + const request = spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: 'unknown-imp-id', + price: 2.5, + w: 300, + h: 250, + adm: '
ad
', + crid: 'creative-unknown' + }] + }] + } + }; + + expect(extractBids(spec.interpretResponse(serverResponse, request))).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('returns iframe sync with encoded consent payload and site id', function () { + const bidRequest = createBidRequest(); + const bidderRequest = createBidderRequest(bidRequest); + spec.buildRequests([bidRequest], bidderRequest); + + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: true }, + [], + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + + const siteSync = findSyncForSite(syncs, 'site-123'); + expect(siteSync).to.not.equal(undefined); + expect(siteSync.type).to.equal('iframe'); + expect(siteSync.url).to.include('https://cdn.pbstck.com/async_usersync.html'); + + const consentPayload = getDecodedSyncPayload(siteSync); + expect(consentPayload).to.deep.equal({ + gdprConsentString: 'consent-string', + gdprApplies: true, + uspConsent: '1YYN', + gpp: 'gpp-string', + gpp_sid: [7, 8] + }); + }); + + it('returns image sync when iframe sync is disabled', function () { + const bidRequest = createBidRequest({ bidId: 'bid-pixel' }); + const bidderRequest = createBidderRequest(bidRequest); + spec.buildRequests([bidRequest], bidderRequest); + + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: true }, + [], + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + + const siteSync = findSyncForSite(syncs, 'site-123'); + expect(siteSync).to.not.equal(undefined); + expect(siteSync.type).to.equal('image'); + expect(siteSync.url).to.include('https://cdn.pbstck.com/async_usersync.png'); + }); + + it('returns no syncs when both iframe and pixel sync are disabled', function () { + const bidRequest = createBidRequest({ bidId: 'bid-disabled-syncs' }); + const bidderRequest = createBidderRequest(bidRequest); + spec.buildRequests([bidRequest], bidderRequest); + + const syncs = spec.getUserSyncs( + { iframeEnabled: false, pixelEnabled: false }, + [], + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + + expect(syncs).to.deep.equal([]); + }); + + it('includes sync entries for each seen site id', function () { + const bidA = createBidRequest({ + bidId: 'bid-site-a', + adUnitCode: 'ad-site-a', + params: { siteId: 'site-a', adUnitName: 'adunit-a' } + }); + const bidB = createBidRequest({ + bidId: 'bid-site-b', + adUnitCode: 'ad-site-b', + params: { siteId: 'site-b', adUnitName: 'adunit-b' } + }); + + spec.buildRequests([bidA], createBidderRequest(bidA)); + spec.buildRequests([bidB], createBidderRequest(bidB)); + + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + baseBidderRequest.gdprConsent, + baseBidderRequest.uspConsent, + baseBidderRequest.gppConsent + ); + const siteIds = syncs.map((sync) => new URL(sync.url).searchParams.get('siteId')); + + expect(siteIds).to.include('site-a'); + expect(siteIds).to.include('site-b'); + }); + + it('supports null consent objects in the sync payload', function () { + const bidRequest = createBidRequest({ + bidId: 'bid-null-consent', + params: { siteId: 'site-null-consent', adUnitName: 'adunit-null-consent' } + }); + spec.buildRequests([bidRequest], createBidderRequest(bidRequest)); + + const syncs = spec.getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + null, + null, + null + ); + + const siteSync = findSyncForSite(syncs, 'site-null-consent'); + expect(siteSync).to.not.equal(undefined); + expect(getDecodedSyncPayload(siteSync)).to.deep.equal({ uspConsent: null }); + }); + }); +}); From 2c19f1bc2c39209baf1b4c6be0f06ec7f7ba3fdd Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Mon, 2 Mar 2026 15:11:49 +0200 Subject: [PATCH 100/105] support multiformat --- modules/taboolaBidAdapter.js | 70 ++-- test/spec/modules/taboolaBidAdapter_spec.js | 340 ++++++++++++++++---- 2 files changed, 327 insertions(+), 83 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index d76b366c5e7..b7b6c984be1 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -183,8 +183,14 @@ const converter = ortbConverter({ return reqData; }, bidResponse(buildBidResponse, bid, context) { - const { mediaType } = getMediaType(context.bidRequest); - context.mediaType = mediaType; + if (bid.mtype === 4) { + context.mediaType = NATIVE; + } else if (bid.mtype === 1) { + context.mediaType = BANNER; + } else { + const { mediaType } = getMediaType(context.bidRequest); + context.mediaType = mediaType; + } if (context.mediaType === NATIVE) { const admObj = safeJSONParse(bid.adm); @@ -223,27 +229,27 @@ export const spec = { 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(hasNative ? {...bid, mediaTypes: {banner: bid.mediaTypes.banner}, nativeOrtbRequest: undefined} : bid); + } + if (hasNative) { + nativeBids.push(hasBanner ? {...bid, mediaTypes: {native: bid.mediaTypes.native}, nativeOrtbRequest: bid.nativeOrtbRequest} : bid); + } }); - const {publisherId} = bidRequest.params; - const { mediaType } = getMediaType(bidRequest); - const baseUrl = mediaType === NATIVE ? NATIVE_ENDPOINT_URL : BANNER_ENDPOINT_URL; - const url = baseUrl + '?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)); + } + if (nativeBids.length) { + requests.push(createTaboolaRequest(nativeBids, bidderRequest, NATIVE_ENDPOINT_URL)); + } + return requests; }, interpretResponse: (serverResponse, request) => { if (!request || !request.bids || !request.data) { @@ -364,6 +370,28 @@ export const spec = { }, }; +function createTaboolaRequest(bidRequests, bidderRequest, endpointUrl) { + const [bidRequest] = bidRequests; + const auctionId = bidderRequest.auctionId || bidRequests[0]?.auctionId; + const data = converter.toORTB({ + bidderRequest: bidderRequest, + bidRequests: bidRequests, + context: { auctionId } + }); + 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 { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index e488ffacef2..45c4f26ce49 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, BANNER_ENDPOINT_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' @@ -270,7 +270,7 @@ 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, @@ -335,7 +335,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'); }); @@ -351,7 +351,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'); }); @@ -372,7 +372,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'); }); @@ -387,7 +387,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); }); @@ -403,7 +403,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'); }); @@ -419,7 +419,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'); }); @@ -428,7 +428,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); }); @@ -437,7 +437,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); }); @@ -446,7 +446,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); }); @@ -475,7 +475,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) @@ -499,7 +499,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) @@ -516,7 +516,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); }); @@ -529,7 +529,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); }); @@ -553,7 +553,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); }); }); @@ -570,7 +570,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) }); @@ -583,7 +583,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]) }); @@ -595,14 +595,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() @@ -619,7 +619,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); }); @@ -633,7 +633,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'); }); @@ -654,7 +654,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'); }); @@ -676,7 +676,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'); }); @@ -694,7 +694,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'); }); @@ -712,7 +712,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'); }); @@ -739,7 +739,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'); }); @@ -753,7 +753,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'); }); @@ -770,7 +770,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; @@ -783,7 +783,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); }); @@ -791,7 +791,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); }); }); @@ -813,7 +813,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const request = spec.buildRequests([defaultBidRequest], bidderRequest); + const [request] = spec.buildRequests([defaultBidRequest], bidderRequest); const serverResponse = { body: { @@ -1100,7 +1100,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: { @@ -1442,7 +1442,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', @@ -1665,7 +1665,11 @@ describe('Taboola Adapter', function () { }, bidId: 'test-bid-id', auctionId: 'test-auction-id', - sizes: [[300, 250]] + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; const commonBidderRequest = { @@ -1678,13 +1682,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'); @@ -1692,7 +1696,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'); @@ -1714,7 +1718,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); @@ -1729,7 +1733,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; }); @@ -1750,7 +1754,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'); @@ -1775,7 +1779,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 { @@ -1788,7 +1792,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; }); @@ -1805,19 +1809,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'); @@ -1836,7 +1840,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); @@ -1854,7 +1858,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 = { @@ -1868,17 +1876,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'); }); @@ -1887,12 +1895,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; }); @@ -1907,7 +1915,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'); @@ -1927,7 +1935,11 @@ describe('Taboola Adapter', function () { bidRequestsCount: 3, bidderRequestsCount: 2, bidderWinsCount: 1, - sizes: [[300, 250]] + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; const commonBidderRequest = { @@ -1941,17 +1953,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); }); @@ -1972,7 +1984,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); @@ -2068,7 +2080,7 @@ describe('Taboola Adapter', function () { auctionId: utils.generateUUID(), }; - const res = spec.buildRequests([nativeBidRequest], commonBidderRequest); + 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'); @@ -2092,7 +2104,7 @@ describe('Taboola Adapter', function () { auctionId: utils.generateUUID(), }; - const res = spec.buildRequests([bannerBidRequest], commonBidderRequest); + 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'); @@ -2121,7 +2133,7 @@ describe('Taboola Adapter', function () { auctionId: utils.generateUUID(), }; - const request = spec.buildRequests([nativeBidRequest], commonBidderRequest); + const [request] = spec.buildRequests([nativeBidRequest], commonBidderRequest); const nativeAdm = { ver: '1.2', @@ -2164,5 +2176,209 @@ describe('Taboola Adapter', function () { }); } }); + + 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); + }); + + it('should use bid.mtype to determine response media type', 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 [request] = spec.buildRequests([nativeBid], 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, + mtype: 4, + 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]).to.not.have.property('ad'); + }); + }); + } }); }) From 3607ace5029cdbe93bc7fdc654d7de9980ed5296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:15:52 -0500 Subject: [PATCH 101/105] Bump actions/upload-artifact from 6 to 7 (#14539) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/PR-assignment-deps.yml | 4 ++-- .github/workflows/jscpd.yml | 6 +++--- .github/workflows/linter.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/PR-assignment-deps.yml b/.github/workflows/PR-assignment-deps.yml index 731cafa691c..2d7fce88837 100644 --- a/.github/workflows/PR-assignment-deps.yml +++ b/.github/workflows/PR-assignment-deps.yml @@ -20,7 +20,7 @@ jobs: run: | npx gulp build - name: Upload dependencies.json - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: dependencies.json path: ./build/dist/dependencies.json @@ -28,7 +28,7 @@ jobs: run: | echo '{ "prNo": ${{ github.event.pull_request.number }} }' >> ${{ runner.temp}}/prInfo.json - name: Upload PR info - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: prInfo path: ${{ runner.temp}}/prInfo.json diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 91b89b018a9..a4b2a14861f 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -56,7 +56,7 @@ jobs: - name: Upload unfiltered jscpd report if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: unfiltered-jscpd-report path: ./jscpd-report.json @@ -89,7 +89,7 @@ jobs: - name: Upload filtered jscpd report if: env.filtered_report_exists == 'true' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: filtered-jscpd-report path: ./filtered-jscpd-report.json @@ -119,7 +119,7 @@ jobs: - name: Upload comment data if: env.filtered_report_exists == 'true' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: comment path: ${{ runner.temp }}/comment.json diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 52825abb00a..e4a736c0b25 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -117,7 +117,7 @@ jobs: - name: Upload comment data if: ${{ steps.comment.outputs.result == 'true' }} - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: comment path: ${{ runner.temp }}/comment.json diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c9386396ae5..73cabecce5e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -206,7 +206,7 @@ jobs: - name: 'Save coverage result' if: ${{ steps.coverage.outputs.coverage }} - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: coverage-partial-${{inputs.test-cmd}}-${{ matrix.chunk-no }} path: ./build/coverage From 2edbe0c75a2582b09e20c75006bbaceeef557a39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:18:13 -0500 Subject: [PATCH 102/105] Bump actions/download-artifact from 7 to 8 (#14540) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 73cabecce5e..924683ec0e0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -229,7 +229,7 @@ jobs: name: ${{ needs.build.outputs.built-key }} - name: Download coverage results - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: ./build/coverage pattern: coverage-partial-${{ inputs.test-cmd }}-* From b6e0f3625aa9af00f293bebea814aa07914c4919 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 2 Mar 2026 14:53:46 +0000 Subject: [PATCH 103/105] Prebid 10.27.0 release --- metadata/modules.json | 63 ++++++++++++------- metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.json | 2 +- metadata/modules/aceexBidAdapter.json | 18 ++++++ 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/adnimationBidAdapter.json | 13 ++++ 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 | 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 | 18 ++++++ 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 | 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 | 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/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/dpaiBidAdapter.json | 13 ++++ 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 | 4 +- 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 | 18 +----- 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/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/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 | 25 ++++++++ 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 | 4 +- .../modules/seedingAllianceBidAdapter.json | 2 +- metadata/modules/seedtagBidAdapter.json | 2 +- metadata/modules/semantiqRtdProvider.json | 2 +- metadata/modules/setupadBidAdapter.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 | 16 ++--- package.json | 2 +- 283 files changed, 435 insertions(+), 341 deletions(-) create mode 100644 metadata/modules/aceexBidAdapter.json create mode 100644 metadata/modules/adnimationBidAdapter.json create mode 100644 metadata/modules/apesterBidAdapter.json create mode 100644 metadata/modules/dpaiBidAdapter.json create mode 100644 metadata/modules/pubstackBidAdapter.json diff --git a/metadata/modules.json b/metadata/modules.json index 39bb38b1059..60c9f672a99 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -43,6 +43,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "aceex", + "aliasOf": null, + "gvlid": 1387, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "acuityads", @@ -659,6 +666,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "adnimation", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "adnow", @@ -1184,6 +1198,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "apester", + "aliasOf": null, + "gvlid": 354, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "appStockSSP", @@ -2122,6 +2143,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "dpai", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "driftpixel", @@ -2829,13 +2857,6 @@ "gvlid": 1358, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "apester", - "aliasOf": "limelightDigital", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "adsyield", @@ -2899,13 +2920,6 @@ "gvlid": null, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "adnimation", - "aliasOf": "limelightDigital", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "rtbdemand", @@ -3858,6 +3872,20 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "pubstack", + "aliasOf": null, + "gvlid": 1408, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "pubstack_server", + "aliasOf": "pubstack", + "gvlid": 1408, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "pubx", @@ -4488,13 +4516,6 @@ "gvlid": 104, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "pubstack", - "aliasOf": null, - "gvlid": 1408, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "sovrn", diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 4b610a461c3..79ecf25a705 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-02-23T16:45:39.806Z", + "timestamp": "2026-03-02T14:44:46.319Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 0ce2459449b..3adce4bba03 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-02-23T16:45:39.909Z", + "timestamp": "2026-03-02T14:44:46.422Z", "disclosures": [] } }, diff --git a/metadata/modules/aceexBidAdapter.json b/metadata/modules/aceexBidAdapter.json new file mode 100644 index 00000000000..f443e609ec4 --- /dev/null +++ b/metadata/modules/aceexBidAdapter.json @@ -0,0 +1,18 @@ +{ + "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", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "aceex", + "aliasOf": null, + "gvlid": 1387, + "disclosureURL": "https://aceex.io/tcf.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index 7b98e95a50d..01786a901ea 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-02-23T16:45:39.911Z", + "timestamp": "2026-03-02T14:44:46.471Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index 92c90104c38..728761cdcc3 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-02-23T16:45:39.945Z", + "timestamp": "2026-03-02T14:44:46.503Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index 7c93f685dc4..01aba8c973b 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-02-23T16:45:40.036Z", + "timestamp": "2026-03-02T14:44:46.575Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index a6dcdef0b1c..c1607d5b2ea 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-02-23T16:45:40.036Z", + "timestamp": "2026-03-02T14:44:46.575Z", "disclosures": [] } }, diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index df69a9d5540..1cd4c70fc97 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-02-23T16:45:40.351Z", + "timestamp": "2026-03-02T14:44:46.894Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index ab8a61481e8..8e01eb95878 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-02-23T16:45:41.307Z", + "timestamp": "2026-03-02T14:45:00.675Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 668b5b6c97a..bec524db96d 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-02-23T16:45:41.308Z", + "timestamp": "2026-03-02T14:45:00.676Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index 092ee382d70..341d5b1c818 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-02-23T16:45:41.663Z", + "timestamp": "2026-03-02T14:45:01.041Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 036a1961d11..54ee67e7037 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-02-23T16:45:41.943Z", + "timestamp": "2026-03-02T14:45:01.314Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index b0b98e5cb55..0516f01d293 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-02-23T16:45:42.098Z", + "timestamp": "2026-03-02T14:45:01.470Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index 959d9b275e9..8b11e6d7d24 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-02-23T16:45:42.136Z", + "timestamp": "2026-03-02T14:45:01.554Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,19 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2026-02-23T16:45:42.136Z", + "timestamp": "2026-03-02T14:45:01.554Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2026-02-23T16:45:42.198Z", + "timestamp": "2026-03-02T14:45:01.604Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2026-02-23T16:45:42.896Z", + "timestamp": "2026-03-02T14:45:02.320Z", "disclosures": [] }, "https://appmonsta.ai/DeviceStorageDisclosure.json": { - "timestamp": "2026-02-23T16:45:42.917Z", + "timestamp": "2026-03-02T14:45:02.337Z", "disclosures": [] } }, diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index e2473a7371a..ded364cbc31 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-02-23T16:45:43.437Z", + "timestamp": "2026-03-02T14:45:02.895Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-02-23T16:45:43.437Z", + "timestamp": "2026-03-02T14:45:02.895Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index 0f2f9e230e4..8f6d6f99397 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-02-23T16:45:43.438Z", + "timestamp": "2026-03-02T14:45:02.896Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index 6b14f9eb08b..f8bd80f3bca 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-02-23T16:45:43.822Z", + "timestamp": "2026-03-02T14:45:03.276Z", "disclosures": [] } }, diff --git a/metadata/modules/adnimationBidAdapter.json b/metadata/modules/adnimationBidAdapter.json new file mode 100644 index 00000000000..7bb7fce194e --- /dev/null +++ b/metadata/modules/adnimationBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "adnimation", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index 7dea0044c43..cb5aa129b20 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-02-23T16:45:43.822Z", + "timestamp": "2026-03-02T14:45:03.276Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index 1f3b4f7666c..c2a643ef408 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-02-23T16:45:44.057Z", + "timestamp": "2026-03-02T14:45:16.546Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index 39503071928..9c3af875f20 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-02-23T16:45:44.386Z", + "timestamp": "2026-03-02T14:45:16.877Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index 1aa5c4d8b9d..c97b093663c 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-02-23T16:45:44.386Z", + "timestamp": "2026-03-02T14:45:16.878Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index abfdb8fd8a0..9a38b9d66e3 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-02-23T16:45:44.944Z", + "timestamp": "2026-03-02T14:45:17.430Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index fb09981c236..7022a062e25 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-02-23T16:45:44.982Z", + "timestamp": "2026-03-02T14:45:17.575Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index 481d7d8641f..6ee1ee7d899 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-02-23T16:45:45.005Z", + "timestamp": "2026-03-02T14:45:17.832Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index 856d53a7cc3..d97e79287da 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-02-23T16:45:45.339Z", + "timestamp": "2026-03-02T14:45:18.165Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 53fabc3f2ac..1683ae8d010 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-02-23T16:45:45.339Z", + "timestamp": "2026-03-02T14:45:18.166Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index 647a9890f50..c0bcc3c0438 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-02-23T16:45:45.472Z", + "timestamp": "2026-03-02T14:45:18.257Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index 2f48665cbf8..643548e6a49 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-02-23T16:45:45.991Z", + "timestamp": "2026-03-02T14:45:18.549Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index b35112b9b94..68c149946c6 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-02-23T16:45:45.991Z", + "timestamp": "2026-03-02T14:45:18.549Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { - "timestamp": "2026-02-23T16:45:46.006Z", + "timestamp": "2026-03-02T14:45:18.568Z", "disclosures": [ { "identifier": "waterFallCacheAnsKey_*", @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-02-23T16:45:46.157Z", + "timestamp": "2026-03-02T14:45:31.321Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index 13229a05a8a..63773fb0465 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-02-23T16:45:46.235Z", + "timestamp": "2026-03-02T14:45:31.395Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index 5920e80f15a..63a0f554c7f 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-02-23T16:45:46.235Z", + "timestamp": "2026-03-02T14:45:31.395Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index 87844aa07e8..80d9bc14b7e 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-02-23T16:45:46.253Z", + "timestamp": "2026-03-02T14:45:31.420Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index de1bb8ebb1a..e127fd2d00b 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-02-23T16:45:46.717Z", + "timestamp": "2026-03-02T14:45:31.871Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index cd39fe92629..a9638ab0a47 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-02-23T16:45:46.745Z", + "timestamp": "2026-03-02T14:45:31.902Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json index 09be6ca136f..0d631041437 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-02-23T16:45:47.028Z", + "timestamp": "2026-03-02T14:45:32.202Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index 2a860a2aaba..8a4900eb3fb 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-02-23T16:45:47.485Z", + "timestamp": "2026-03-02T14:45:32.667Z", "disclosures": [] } }, diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index 7b6aafe01dd..a204078442a 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-02-23T16:45:47.524Z", + "timestamp": "2026-03-02T14:45:32.757Z", "disclosures": [] } }, diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index b8deecd8258..0bc1a7c7752 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-02-23T16:45:47.524Z", + "timestamp": "2026-03-02T14:45:32.758Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 8260cbedc78..7593b8bcb11 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-02-23T16:45:47.614Z", + "timestamp": "2026-03-02T14:45:32.828Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/apesterBidAdapter.json b/metadata/modules/apesterBidAdapter.json new file mode 100644 index 00000000000..372e0ba6a78 --- /dev/null +++ b/metadata/modules/apesterBidAdapter.json @@ -0,0 +1,18 @@ +{ + "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", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "apester", + "aliasOf": null, + "gvlid": 354, + "disclosureURL": "https://apester.com/deviceStorage.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index 4cca97a2c61..8d113568028 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-02-23T16:45:47.819Z", + "timestamp": "2026-03-02T14:45:33.167Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index 59823893662..bc805982843 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-02-23T16:45:47.845Z", + "timestamp": "2026-03-02T14:45:33.203Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index 575475e3f1d..59027a399d7 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-02-23T16:45:48.607Z", + "timestamp": "2026-03-02T14:45:33.960Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2026-02-23T16:45:48.018Z", + "timestamp": "2026-03-02T14:45:33.279Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2026-02-23T16:45:48.146Z", + "timestamp": "2026-03-02T14:45:33.420Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2026-02-23T16:45:48.607Z", + "timestamp": "2026-03-02T14:45:33.960Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index f08a1d0abc5..15019d11110 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-02-23T16:45:48.625Z", + "timestamp": "2026-03-02T14:45:33.989Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json index c5f467eb5b9..7132ee483e9 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-02-23T16:45:48.698Z", + "timestamp": "2026-03-02T14:45:34.055Z", "disclosures": [ { "identifier": "vendor-id", diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index 51abe574f23..b7a2fb2671d 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-02-23T16:45:48.905Z", + "timestamp": "2026-03-02T14:45:34.071Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index 425d6c403b5..ad7d2d71fc5 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-02-23T16:45:48.947Z", + "timestamp": "2026-03-02T14:45:34.115Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index 3c7f723af4d..33ade7a617c 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-02-23T16:45:49.244Z", + "timestamp": "2026-03-02T14:45:34.167Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 7d4a7482075..198266cb6ac 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-02-23T16:45:49.284Z", + "timestamp": "2026-03-02T14:45:34.210Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index bca8af342af..b7f18cad4eb 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-02-23T16:45:49.309Z", + "timestamp": "2026-03-02T14:45:34.236Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 4abf52c58f3..84b4ecc7f33 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-02-23T16:45:49.662Z", + "timestamp": "2026-03-02T14:45:34.257Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index 7b7c771fb56..b48dadd84d0 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-02-23T16:45:49.787Z", + "timestamp": "2026-03-02T14:45:34.379Z", "disclosures": [] } }, diff --git a/metadata/modules/bidfuseBidAdapter.json b/metadata/modules/bidfuseBidAdapter.json index 8ec350d8e5f..cb5dc975164 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-02-23T16:45:49.844Z", + "timestamp": "2026-03-02T14:45:34.436Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index 70f05686f72..320c69b6e66 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-02-12T16:02:35.232Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:34.611Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index 28932b2ac27..987e79d876e 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-02-23T16:45:50.078Z", + "timestamp": "2026-03-02T14:45:34.658Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index a221774de81..9ae146cb5de 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-02-23T16:45:50.372Z", + "timestamp": "2026-03-02T14:45:34.957Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index 6ca672e3894..b3c53c81f31 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-02-23T16:45:50.669Z", + "timestamp": "2026-03-02T14:45:35.252Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index 08accddbd72..fdefc12b8e7 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-02-23T16:45:50.841Z", + "timestamp": "2026-03-02T14:45:35.438Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index 7d4f03a43f3..59d3a1914dc 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-02-23T16:45:51.242Z", + "timestamp": "2026-03-02T14:45:35.784Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index b99fd90c052..e8f694fd2b9 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-02-23T16:45:51.273Z", + "timestamp": "2026-03-02T14:45:35.801Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 5e83165008b..4475c15ed3a 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-02-23T16:45:51.292Z", + "timestamp": "2026-03-02T14:45:35.828Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index 202ed38c166..ddc532d4e32 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-02-23T16:45:51.438Z", + "timestamp": "2026-03-02T14:45:35.966Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index c63668fe614..571e8dac8d9 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-02-23T16:45:51.522Z", + "timestamp": "2026-03-02T14:45:36.025Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index d87a42ad403..06cedda2cbb 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-02-23T16:45:51.927Z", + "timestamp": "2026-03-02T14:45:36.084Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index 3283b263af3..1b8ecd5185e 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-02-23T16:45:39.804Z", + "timestamp": "2026-03-02T14:44:46.317Z", "disclosures": [ { "identifier": "iabToFwMappingkey", diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index e64878ea4cb..414cf9f61c3 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-02-23T16:45:52.241Z", + "timestamp": "2026-03-02T14:45:36.379Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index 53cb3b5385d..0374f04c2e3 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-02-23T16:45:52.571Z", + "timestamp": "2026-03-02T14:45:36.729Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index 6d53c6e853b..ec57e47521f 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-02-23T16:45:52.572Z", + "timestamp": "2026-03-02T14:45:36.730Z", "disclosures": [] } }, diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index b244dc6ce23..145961029d1 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-02-23T16:45:52.988Z", + "timestamp": "2026-03-02T14:45:37.148Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index 18aafeda009..41d01c918e0 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-02-23T16:45:53.004Z", + "timestamp": "2026-03-02T14:45:37.166Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index 7683ce8538d..a1b7a772220 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-02-23T16:45:53.031Z", + "timestamp": "2026-03-02T14:45:37.190Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index 4a923ffa33f..0cd76902e9b 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-02-23T16:45:53.109Z", + "timestamp": "2026-03-02T14:45:37.270Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index a0d2b9aed0c..96bc8e141d6 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-02-23T16:45:53.187Z", + "timestamp": "2026-03-02T14:45:37.290Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index 242e014398c..7b5a61930c9 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-02-23T16:45:53.622Z", + "timestamp": "2026-03-02T14:45:37.325Z", "disclosures": null } }, diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index f6f6faad13d..5999b1b9c9a 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-02-23T16:45:54.632Z", + "timestamp": "2026-03-02T14:45:37.366Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 7448ea8dcd0..1bc771d2901 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-02-23T16:45:54.677Z", + "timestamp": "2026-03-02T14:45:37.458Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index affad4ec7c7..255976fa486 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-02-23T16:45:54.802Z", + "timestamp": "2026-03-02T14:45:37.500Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index 5520f75a569..fb94d78e961 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-02-23T16:45:54.838Z", + "timestamp": "2026-03-02T14:45:37.599Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index 4faac441ed3..d0e56b65ba1 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-02-23T16:45:54.851Z", + "timestamp": "2026-03-02T14:45:37.624Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index 4b04b4f76e2..265357a9c7f 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-02-23T16:45:54.851Z", + "timestamp": "2026-03-02T14:45:37.625Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index 6dfc197ae34..7a945949fe0 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-02-23T16:45:54.952Z", + "timestamp": "2026-03-02T14:45:37.988Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index 9ea7adb6c9e..6779bc9b8a9 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-02-23T16:45:55.362Z", + "timestamp": "2026-03-02T14:45:38.394Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index 239dc523417..d8035174738 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-02-23T16:45:39.803Z", + "timestamp": "2026-03-02T14:44:46.316Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index 41142e3663e..cc0c299d4f7 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-02-23T16:45:55.469Z", + "timestamp": "2026-03-02T14:45:38.686Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index f2fb3929675..ca2f76b4695 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-02-23T16:45:55.817Z", + "timestamp": "2026-03-02T14:45:39.098Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index 864b1e03246..1ee3130984b 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-02-23T16:45:56.279Z", + "timestamp": "2026-03-02T14:45:44.672Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 5e6606fb5a4..8d8e30c4102 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-02-23T16:45:56.279Z", + "timestamp": "2026-03-02T14:45:44.672Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index 455b19c7a00..b19604b5704 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-02-23T16:46:10.165Z", + "timestamp": "2026-03-02T14:45:45.048Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index 924aa537c52..676eab6e3c8 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-02-23T16:46:10.392Z", + "timestamp": "2026-03-02T14:45:45.334Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index f49cf65cfdb..14a0769efca 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-02-23T16:46:11.142Z", + "timestamp": "2026-03-02T14:45:46.086Z", "disclosures": [] } }, diff --git a/metadata/modules/dpaiBidAdapter.json b/metadata/modules/dpaiBidAdapter.json new file mode 100644 index 00000000000..901b09a3355 --- /dev/null +++ b/metadata/modules/dpaiBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "dpai", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 9b5a628f3ee..579ccaef7ac 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-02-23T16:46:11.143Z", + "timestamp": "2026-03-02T14:45:46.087Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 32dff8c4b9f..1410a3e4917 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-02-23T16:46:11.788Z", + "timestamp": "2026-03-02T14:45:46.760Z", "disclosures": [] } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index 7675689c619..58cc40d3ace 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-02-23T16:46:12.128Z", + "timestamp": "2026-03-02T14:45:47.084Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index 1f472770d2f..5b8c1e60d93 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-02-23T16:46:12.175Z", + "timestamp": "2026-03-02T14:45:47.136Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index b80b81ce7d1..a618d1b2dd7 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-02-23T16:46:12.205Z", + "timestamp": "2026-03-02T14:45:47.169Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index 05a2cfaf124..8908f454cef 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-02-23T16:46:12.239Z", + "timestamp": "2026-03-02T14:45:47.201Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index 7954882ec63..db71948ec37 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-02-23T16:46:12.258Z", + "timestamp": "2026-03-02T14:45:47.226Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 52f7c75e179..5d3d333813e 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-02-23T16:46:12.836Z", + "timestamp": "2026-03-02T14:45:47.806Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 072c7c51e68..4f78604a882 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-02-23T16:46:13.078Z", + "timestamp": "2026-03-02T14:45:48.013Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index 5b852097ea3..898c77b0f70 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-02-23T16:46:16.911Z", + "timestamp": "2026-03-02T14:45:48.194Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index fe615c7a678..c950b152e00 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-02-23T16:46:17.030Z", + "timestamp": "2026-03-02T14:45:48.313Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index 9d4449f7dad..05ce430c856 100644 --- a/metadata/modules/gamoshiBidAdapter.json +++ b/metadata/modules/gamoshiBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.gamoshi.com/disclosures-client-storage.json": { - "timestamp": "2026-02-12T16:02:45.331Z", - "disclosures": [] + "timestamp": "2026-03-02T14:45:48.653Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index c2cc3a39847..9aa5b49be2d 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-02-23T16:46:19.113Z", + "timestamp": "2026-03-02T14:45:49.814Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 4e1a3568be9..3ad4e7c7df2 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-02-23T16:46:19.114Z", + "timestamp": "2026-03-02T14:45:49.816Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 266a3a47920..61b5f9f87a3 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-02-23T16:46:19.139Z", + "timestamp": "2026-03-02T14:45:49.839Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index fbebc8a5b71..0420f51b838 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-02-23T16:46:19.169Z", + "timestamp": "2026-03-02T14:45:49.864Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index 9a89dd3db7b..9131fda39bc 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-02-23T16:46:19.328Z", + "timestamp": "2026-03-02T14:45:49.990Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index d2faf64c6e6..a02dd109e19 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-02-23T16:46:19.392Z", + "timestamp": "2026-03-02T14:45:50.048Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index 93ec7d55d3d..b5eb21067e8 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-02-23T16:46:19.493Z", + "timestamp": "2026-03-02T14:45:50.186Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/harionBidAdapter.json b/metadata/modules/harionBidAdapter.json index 6a7fe43d137..dc71450537a 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-02-23T16:46:19.494Z", + "timestamp": "2026-03-02T14:45:50.186Z", "disclosures": [] } }, diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index 9e5f60d4e75..d5fc01d7c33 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-02-23T16:46:19.844Z", + "timestamp": "2026-03-02T14:45:50.576Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index b65b5d8d4a8..efc1583902c 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-02-23T16:46:20.115Z", + "timestamp": "2026-03-02T14:45:50.868Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index a67ab589a06..886832932a7 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-02-23T16:46:20.421Z", + "timestamp": "2026-03-02T14:45:51.113Z", "disclosures": [ { "identifier": "id5id", diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index 3a611244109..03bcaedd660 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-02-23T16:46:20.700Z", + "timestamp": "2026-03-02T14:45:51.402Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index 7c87299d926..eadb5eea6ca 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-02-23T16:46:20.718Z", + "timestamp": "2026-03-02T14:45:51.424Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index a6aaeabad08..a84d454fd03 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-02-23T16:46:21.047Z", + "timestamp": "2026-03-02T14:45:51.728Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 0c7a398cffb..a6e1e421e03 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-02-23T16:46:21.326Z", + "timestamp": "2026-03-02T14:45:52.052Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index acd77ff74fc..818bc7ee34a 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-02-23T16:46:21.327Z", + "timestamp": "2026-03-02T14:45:52.052Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index f19aa191649..93ac9d8e09a 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-02-23T16:46:21.362Z", + "timestamp": "2026-03-02T14:45:52.089Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/insuradsBidAdapter.json b/metadata/modules/insuradsBidAdapter.json index cbd5502eed4..05a0e236aad 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-02-23T16:46:21.432Z", + "timestamp": "2026-03-02T14:45:52.162Z", "disclosures": [ { "identifier": "___iat_ses", diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 09702d55af8..43fe76d2cad 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-02-23T16:46:21.553Z", + "timestamp": "2026-03-02T14:45:52.351Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index cce1631883e..9b23ea0bc54 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-02-23T16:46:21.605Z", + "timestamp": "2026-03-02T14:45:52.421Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 95e81ce7106..7f9b4c104c2 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-02-23T16:46:21.954Z", + "timestamp": "2026-03-02T14:45:52.785Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index d77169ebf76..ba7581331d0 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-02-23T16:46:22.404Z", + "timestamp": "2026-03-02T14:45:53.259Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index 7158bab3abe..f20fbceea31 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-02-23T16:46:25.271Z", + "timestamp": "2026-03-02T14:45:53.334Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index 6fcc8ee22ea..7fb5438d310 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-02-23T16:46:25.791Z", + "timestamp": "2026-03-02T14:45:53.863Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 38ed4cfc3fc..6695015fb1e 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-02-23T16:46:25.812Z", + "timestamp": "2026-03-02T14:45:53.882Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index b520862c201..18c7713446a 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-02-23T16:46:26.041Z", + "timestamp": "2026-03-02T14:45:54.165Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index 886600913c3..38316d32257 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-02-23T16:46:26.065Z", + "timestamp": "2026-03-02T14:45:54.193Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index 8431c4f79f5..14e44dfc1e0 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-02-23T16:46:26.136Z", + "timestamp": "2026-03-02T14:45:54.240Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-02-23T16:46:26.206Z", + "timestamp": "2026-03-02T14:45:54.278Z", "disclosures": [] } }, @@ -32,13 +32,6 @@ "gvlid": 1358, "disclosureURL": "https://policy.iion.io/deviceStorage.json" }, - { - "componentType": "bidder", - "componentName": "apester", - "aliasOf": "limelightDigital", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "adsyield", @@ -102,13 +95,6 @@ "gvlid": null, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "adnimation", - "aliasOf": "limelightDigital", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "rtbdemand", diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index f60e74c1825..73069893950 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-02-23T16:46:26.206Z", + "timestamp": "2026-03-02T14:45:54.278Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index d0bc2c5c444..8c36b5b69d2 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-02-23T16:46:26.218Z", + "timestamp": "2026-03-02T14:45:54.418Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 1cd5d2d128c..a5fb8083be9 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-02-23T16:46:26.219Z", + "timestamp": "2026-03-02T14:45:54.419Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index fee1f412589..33632aaca12 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-02-23T16:46:26.249Z", + "timestamp": "2026-03-02T14:45:54.453Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 1019ad44e25..1a84524e9d6 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-02-23T16:46:26.732Z", + "timestamp": "2026-03-02T14:45:54.576Z", "disclosures": [ { "identifier": "_cc_id", diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 61100089726..96e987d9dc2 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-02-23T16:46:26.798Z", + "timestamp": "2026-03-02T14:45:54.640Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 990477cdb7b..096ca78f06b 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-02-23T16:46:27.273Z", + "timestamp": "2026-03-02T14:45:55.051Z", "disclosures": [] } }, diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index ba0e333fa38..e20881844c8 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-02-23T16:46:27.628Z", + "timestamp": "2026-03-02T14:45:55.409Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index 7c796c8155c..19b9a989b2c 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-02-23T16:46:27.759Z", + "timestamp": "2026-03-02T14:45:55.518Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index b2cc478cf54..43aee7b5d99 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-02-23T16:46:28.044Z", + "timestamp": "2026-03-02T14:45:55.650Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index 74390863c39..64dfcf767a5 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-02-23T16:46:28.059Z", + "timestamp": "2026-03-02T14:45:55.669Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index 9d59e87ea4a..ffa894357ad 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-02-23T16:46:28.059Z", + "timestamp": "2026-03-02T14:45:55.670Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index 2a5e75627b9..c41604e62be 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-02-23T16:46:28.120Z", + "timestamp": "2026-03-02T14:45:55.769Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index 5a1c732c5de..b62853ac937 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-02-23T16:46:28.405Z", + "timestamp": "2026-03-02T14:45:56.051Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2026-02-23T16:46:28.464Z", + "timestamp": "2026-03-02T14:45:56.188Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index aac7269dadc..b3c225207bc 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-02-23T16:46:29.446Z", + "timestamp": "2026-03-02T14:45:56.239Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index 667c32cd604..d6da81c2fe5 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-02-23T16:46:29.993Z", + "timestamp": "2026-03-02T14:45:56.794Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index 550a82a422c..d6dd6937212 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-02-23T16:46:30.399Z", + "timestamp": "2026-03-02T14:45:56.864Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index 554cc915038..d0da32736ce 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-02-23T16:46:30.399Z", + "timestamp": "2026-03-02T14:45:56.864Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index f031c7ba114..9908f3b78ee 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-02-23T16:46:30.399Z", + "timestamp": "2026-03-02T14:45:56.865Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index ebc86cfe7ce..14a16812ce4 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-02-23T16:46:30.434Z", + "timestamp": "2026-03-02T14:45:56.886Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index a942bc0147f..1ce8457b058 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-02-23T16:46:30.523Z", + "timestamp": "2026-03-02T14:45:56.944Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index ecada798667..3982ab341b1 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-02-23T16:46:30.655Z", + "timestamp": "2026-03-02T14:45:57.043Z", "disclosures": null } }, diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index e9b37f0ed11..8f9ed0091ef 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-02-23T16:46:30.691Z", + "timestamp": "2026-03-02T14:45:57.065Z", "disclosures": null } }, diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index d862ff06784..8ac1b408eae 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-02-23T16:46:30.691Z", + "timestamp": "2026-03-02T14:45:57.066Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index 5cf3f47c826..2a3f0c5c99a 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-02-23T16:46:30.699Z", + "timestamp": "2026-03-02T14:45:57.067Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index 833339739a4..59662e64c8a 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-02-23T16:46:31.031Z", + "timestamp": "2026-03-02T14:45:57.381Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index ef9353ef688..57fd3223351 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-02-23T16:46:31.054Z", + "timestamp": "2026-03-02T14:45:57.429Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index 8ee05edf5e9..d57a7a52695 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-02-23T16:46:31.054Z", + "timestamp": "2026-03-02T14:45:57.430Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index fd4d8551d20..f366a7a8120 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-02-23T16:46:31.132Z", + "timestamp": "2026-03-02T14:45:57.516Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 32c4f7b07fa..00c1ef09267 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-02-23T16:46:32.174Z", + "timestamp": "2026-03-02T14:45:58.404Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2026-02-23T16:46:31.398Z", + "timestamp": "2026-03-02T14:45:57.804Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2026-02-23T16:46:31.421Z", + "timestamp": "2026-03-02T14:45:57.826Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-02-23T16:46:31.648Z", + "timestamp": "2026-03-02T14:45:57.949Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,7 +46,7 @@ ] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2026-02-23T16:46:31.648Z", + "timestamp": "2026-03-02T14:45:57.949Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -61,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2026-02-23T16:46:31.797Z", + "timestamp": "2026-03-02T14:45:58.021Z", "disclosures": [] } }, diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index d697808a02e..056f7d06193 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-02-23T16:46:32.174Z", + "timestamp": "2026-03-02T14:45:58.405Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 5fac928bc8b..3c0a03590fe 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-02-23T16:46:32.338Z", + "timestamp": "2026-03-02T14:45:58.420Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index a5fa44903e0..704ad832740 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-02-23T16:46:34.054Z", + "timestamp": "2026-03-02T14:46:00.252Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index 4fcb8df58e4..e0eda9c248a 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-02-23T16:46:34.443Z", + "timestamp": "2026-03-02T14:46:00.621Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index 8eb0796b3c8..b9cfde98549 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-02-23T16:46:34.510Z", + "timestamp": "2026-03-02T14:46:00.670Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index 101bcbf8ef2..bf3d6e763b9 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-02-23T16:46:34.569Z", + "timestamp": "2026-03-02T14:46:00.723Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 508199c4060..94e1cbede34 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-02-23T16:46:34.570Z", + "timestamp": "2026-03-02T14:46:00.724Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index ffa1a04f0a0..b4ee396237c 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-02-23T16:46:34.912Z", + "timestamp": "2026-03-02T14:46:01.014Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index d7dd135c570..dfb10023709 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-02-23T16:46:34.950Z", + "timestamp": "2026-03-02T14:46:01.052Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index cfa23f46ee5..be92865dfd2 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-02-23T16:46:34.995Z", + "timestamp": "2026-03-02T14:46:01.082Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index 40c446b1728..51ed301458d 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-02-23T16:46:35.155Z", + "timestamp": "2026-03-02T14:46:01.269Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index 66165aa5587..fef02111513 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-02-23T16:46:35.204Z", + "timestamp": "2026-03-02T14:46:01.427Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index 1d348223a0f..b9571894c8a 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-02-23T16:46:35.462Z", + "timestamp": "2026-03-02T14:46:01.687Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index 32d23be29fa..325c70c587c 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-02-23T16:46:35.776Z", + "timestamp": "2026-03-02T14:46:02.006Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index cc8ac6fa341..23d5ad8eb09 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-02-23T16:46:35.993Z", + "timestamp": "2026-03-02T14:46:02.249Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index 8e8904cf7ff..d4c804fcc8c 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-02-23T16:46:36.196Z", + "timestamp": "2026-03-02T14:46:02.471Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json index 559f347c9a8..e06de639217 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-02-23T16:46:36.216Z", + "timestamp": "2026-03-02T14:46:02.495Z", "disclosures": [ { "identifier": "panxo_uid", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index 4b4f986fab5..5bdea8a1dd9 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-02-23T16:46:36.601Z", + "timestamp": "2026-03-02T14:46:02.691Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index 7e186152215..647b813dccd 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-02-23T16:46:37.007Z", + "timestamp": "2026-03-02T14:46:03.107Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 9f953274741..0f41067e138 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-02-23T16:46:37.198Z", + "timestamp": "2026-03-02T14:46:03.283Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index 6b5ede1ed9f..c04bfe3f164 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-02-23T16:46:37.199Z", + "timestamp": "2026-03-02T14:46:03.285Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index 4fefc69bc62..56ff6f0ebc5 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-02-23T16:46:37.264Z", + "timestamp": "2026-03-02T14:46:03.345Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 80a6aa15d93..3ef5aaa70c7 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-02-23T16:45:39.802Z", + "timestamp": "2026-03-02T14:44:46.315Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-02-23T16:45:39.802Z", + "timestamp": "2026-03-02T14:44:46.315Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index d1f28334f85..63ae55fec56 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-02-23T16:46:37.437Z", + "timestamp": "2026-03-02T14:46:03.527Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index 9c67c55703e..b1668166cfa 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-02-23T16:46:37.668Z", + "timestamp": "2026-03-02T14:46:03.818Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index f73f75e6a2e..e88a5f370ba 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-02-23T16:46:37.670Z", + "timestamp": "2026-03-02T14:46:03.818Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 788c756279d..905a96a3b49 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-02-23T16:46:37.731Z", + "timestamp": "2026-03-02T14:46:03.882Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index bb87745635d..7ad66a8914c 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-02-23T16:46:38.272Z", + "timestamp": "2026-03-02T14:46:04.343Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index d1ceb3861d2..7dc64f1d033 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-02-23T16:46:38.273Z", + "timestamp": "2026-03-02T14:46:04.343Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index 96de8153bd2..2545e56212f 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-02-23T16:46:38.326Z", + "timestamp": "2026-03-02T14:46:04.569Z", "disclosures": [] } }, diff --git a/metadata/modules/pubstackBidAdapter.json b/metadata/modules/pubstackBidAdapter.json new file mode 100644 index 00000000000..5081e22a00d --- /dev/null +++ b/metadata/modules/pubstackBidAdapter.json @@ -0,0 +1,25 @@ +{ + "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", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "pubstack", + "aliasOf": null, + "gvlid": 1408, + "disclosureURL": "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json" + }, + { + "componentType": "bidder", + "componentName": "pubstack_server", + "aliasOf": "pubstack", + "gvlid": 1408, + "disclosureURL": "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index a99d1500aef..3134a58466f 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-02-23T16:46:38.328Z", + "timestamp": "2026-03-02T14:46:04.603Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index d5376ad540f..0b8de794a6c 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-02-23T16:46:38.345Z", + "timestamp": "2026-03-02T14:46:04.619Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json index c4824387f0e..0ac7d60db4a 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-02-23T16:46:38.525Z", + "timestamp": "2026-03-02T14:46:04.798Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index c30e8a48fb0..673069e3845 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-02-23T16:46:38.536Z", + "timestamp": "2026-03-02T14:46:04.800Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index 52d1e7e94ed..752bc1a2f76 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-02-23T16:46:38.907Z", + "timestamp": "2026-03-02T14:46:05.271Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index f8b1ebdb359..622cc457012 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-02-23T16:46:38.943Z", + "timestamp": "2026-03-02T14:46:05.294Z", "disclosures": null } }, diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index 012ce5e965e..5addfe8ad58 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-02-23T16:46:39.813Z", + "timestamp": "2026-03-02T14:46:06.100Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index 0f76baa147d..dd359941cee 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-02-23T16:46:40.101Z", + "timestamp": "2026-03-02T14:46:06.261Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index bc05a7b7e84..37dc05e23e9 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-02-23T16:46:40.139Z", + "timestamp": "2026-03-02T14:46:06.304Z", "disclosures": [] } }, diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index f632ce44325..4c36e64fbd2 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-02-23T16:46:40.186Z", + "timestamp": "2026-03-02T14:46:06.345Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json index 5ecb6aa4bf9..6a2c2b0b465 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-02-23T16:46:40.251Z", + "timestamp": "2026-03-02T14:46:06.391Z", "disclosures": [] } }, diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index cc403f3bc41..b15321eabee 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-02-23T16:46:40.374Z", + "timestamp": "2026-03-02T14:46:06.448Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index a4356846e9c..10611865e4a 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-02-23T16:46:40.704Z", + "timestamp": "2026-03-02T14:46:06.805Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index f91ed10f678..3891bd224cb 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-02-23T16:46:40.772Z", + "timestamp": "2026-03-02T14:46:06.861Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-02-23T16:46:40.772Z", + "timestamp": "2026-03-02T14:46:06.861Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 7350dc1a269..980500fae02 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-02-23T16:46:40.772Z", + "timestamp": "2026-03-02T14:46:06.862Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index 2ce97f53637..31714df7b2e 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-02-23T16:46:40.789Z", + "timestamp": "2026-03-02T14:46:06.887Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index 17e1ff321e2..68ecbd8f4a9 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-02-23T16:46:41.009Z", + "timestamp": "2026-03-02T14:46:07.020Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index 9ad2e289dc2..b39b894bd9f 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-02-23T16:46:41.251Z", + "timestamp": "2026-03-02T14:46:07.254Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 612712787f7..1772818cedc 100644 --- a/metadata/modules/screencoreBidAdapter.json +++ b/metadata/modules/screencoreBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://screencore.io/tcf.json": { - "timestamp": "2026-02-23T16:46:41.270Z", - "disclosures": null + "timestamp": "2026-03-02T14:46:07.274Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index a5cae979bb5..801af2fb55e 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-02-23T16:46:43.868Z", + "timestamp": "2026-03-02T14:46:07.339Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index 994b6f1a103..3ad01be8a3a 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-02-23T16:46:43.971Z", + "timestamp": "2026-03-02T14:46:07.366Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index cc7548e32e5..296ab8e1865 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-02-23T16:46:43.971Z", + "timestamp": "2026-03-02T14:46:07.366Z", "disclosures": [] } }, diff --git a/metadata/modules/setupadBidAdapter.json b/metadata/modules/setupadBidAdapter.json index 4411f2126c2..20bb4dab06f 100644 --- a/metadata/modules/setupadBidAdapter.json +++ b/metadata/modules/setupadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cookies.stpd.cloud/disclosures.json": { - "timestamp": "2026-02-23T16:46:44.040Z", + "timestamp": "2026-03-02T14:46:07.471Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index 8b4155bad91..4326d208d40 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-02-23T16:46:44.168Z", + "timestamp": "2026-03-02T14:46:07.609Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index d387202b43c..cda116dbd54 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-02-23T16:46:44.313Z", + "timestamp": "2026-03-02T14:46:07.758Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index 89c00185e6e..8c2ce012b1d 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-02-23T16:46:44.313Z", + "timestamp": "2026-03-02T14:46:07.758Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index 9c73af19048..9abcf1c23b4 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-02-23T16:46:44.338Z", + "timestamp": "2026-03-02T14:46:07.777Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index 3226ae43a1f..c5aa4e9afcb 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-02-23T16:46:44.845Z", + "timestamp": "2026-03-02T14:46:08.247Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index 0c54f8de6de..348ce7f9fd4 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-02-23T16:46:44.860Z", + "timestamp": "2026-03-02T14:46:08.262Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index 3d218dd6936..413fb0f4ca3 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-02-23T16:46:45.210Z", + "timestamp": "2026-03-02T14:46:08.585Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index b17d0a46919..a1c3212113e 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-02-23T16:46:45.331Z", + "timestamp": "2026-03-02T14:46:08.665Z", "disclosures": [] } }, diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 56af6c1fe90..06854dd00c3 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-02-23T16:46:45.331Z", + "timestamp": "2026-03-02T14:46:08.666Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index eba853e8c5f..202d8317bad 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-02-23T16:46:45.348Z", + "timestamp": "2026-03-02T14:46:08.685Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index ac23b467ca0..7b14099448a 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-02-23T16:46:45.388Z", + "timestamp": "2026-03-02T14:46:08.723Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index 92f87aff58a..ef1115d5554 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-02-23T16:46:45.853Z", + "timestamp": "2026-03-02T14:46:09.181Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index 6ae3c2d0521..b75cfe08e7c 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-02-23T16:46:45.887Z", + "timestamp": "2026-03-02T14:46:09.233Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index 9af60365ea1..6ebb47d3e03 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-02-23T16:46:46.110Z", + "timestamp": "2026-03-02T14:46:09.460Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index 0cfeb10dd67..0abfb9c0b85 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-02-23T16:46:46.339Z", + "timestamp": "2026-03-02T14:46:09.690Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index fd0a4545adb..b89e3d7a3d7 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-02-23T16:46:46.358Z", + "timestamp": "2026-03-02T14:46:09.709Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index 74e7b5f7b4e..be24aefdf2e 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-02-23T16:46:46.867Z", + "timestamp": "2026-03-02T14:46:09.989Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index 6751dd820fb..a99dc0c8ea5 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-02-23T16:46:47.561Z", + "timestamp": "2026-03-02T14:46:10.636Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index 805fbbb0997..6a53c79a6f2 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-02-23T16:46:47.562Z", + "timestamp": "2026-03-02T14:46:10.637Z", "disclosures": [ { "identifier": "sa-camp-*", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index bd992ea772c..62dadaf9188 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-02-23T16:46:47.608Z", + "timestamp": "2026-03-02T14:46:10.672Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index d198134cd6b..4830347118e 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-02-23T16:46:47.628Z", + "timestamp": "2026-03-02T14:46:10.688Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 1f780caaf60..0409bb9984d 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-02-23T16:46:48.003Z", + "timestamp": "2026-03-02T14:46:11.041Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index ec6552c31a3..babdae509ce 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-02-23T16:46:48.633Z", + "timestamp": "2026-03-02T14:46:11.678Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index 04e6ee9448c..c829cd46687 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-02-23T16:46:48.896Z", + "timestamp": "2026-03-02T14:46:11.938Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index facd96483c0..6fcf6a5ec28 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-02-23T16:46:49.649Z", + "timestamp": "2026-03-02T14:46:12.149Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index 2883fb9359b..35fb4310c9c 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-02-23T16:46:49.649Z", + "timestamp": "2026-03-02T14:46:12.150Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index 80a5faf1393..6a1843cea0d 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-02-23T16:46:49.676Z", + "timestamp": "2026-03-02T14:46:12.408Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index 9d2f8d9eb10..d785b33951a 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-02-23T16:46:49.705Z", + "timestamp": "2026-03-02T14:46:12.436Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index f7bde1849e1..669db4c09d6 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-02-23T16:46:49.705Z", + "timestamp": "2026-03-02T14:46:12.437Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index e87eaea62c9..a84486229b0 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-02-23T16:46:49.723Z", + "timestamp": "2026-03-02T14:46:12.457Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index 9e70bdfb084..fb6a14599bd 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-02-23T16:46:49.723Z", + "timestamp": "2026-03-02T14:46:12.457Z", "disclosures": [] } }, diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index eda93c2d7e9..dd50f1b4493 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-02-23T16:46:49.770Z", + "timestamp": "2026-03-02T14:46:12.515Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index 80539abb3c4..e4683b84727 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-02-23T16:45:39.803Z", + "timestamp": "2026-03-02T14:44:46.316Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index 9da24e24f42..719b4868651 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-02-23T16:46:49.802Z", + "timestamp": "2026-03-02T14:46:12.534Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index 749692885ff..ae8f461ead0 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-02-23T16:46:49.901Z", + "timestamp": "2026-03-02T14:46:12.661Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index a867b85ec31..825c0388f7a 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-02-23T16:46:49.930Z", + "timestamp": "2026-03-02T14:46:12.692Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index 559fb3fd5c4..fc4095423c5 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-02-23T16:46:49.930Z", + "timestamp": "2026-03-02T14:46:12.692Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index 2628349ae18..dbd497898d7 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-02-23T16:47:13.633Z", + "timestamp": "2026-03-02T14:46:12.772Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index a358ef5eba5..0b02483de93 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-02-23T16:47:13.661Z", + "timestamp": "2026-03-02T14:46:12.790Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index 9d874319e3e..cc27f390b4d 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-02-23T16:47:13.761Z", + "timestamp": "2026-03-02T14:46:12.878Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index bb45d2ed3b7..5d00220a7b8 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-02-23T16:47:13.762Z", + "timestamp": "2026-03-02T14:46:12.878Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index 0ba454ed268..a7288064da5 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-02-23T16:45:39.804Z", + "timestamp": "2026-03-02T14:44:46.317Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index 07224a7afd7..6e7e629b0ac 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-02-23T16:47:13.762Z", + "timestamp": "2026-03-02T14:46:12.878Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index 4a4449b21b1..8e9e76248b8 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-02-23T16:47:13.763Z", + "timestamp": "2026-03-02T14:46:12.879Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index eda44e25e76..05f5921394d 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-02-23T16:45:39.803Z", + "timestamp": "2026-03-02T14:44:46.316Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index fe471a8ca81..4bcb9939955 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-02-23T16:47:13.763Z", + "timestamp": "2026-03-02T14:46:12.879Z", "disclosures": [] } }, diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index d893b26cc36..d2fee191de4 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-02-23T16:47:13.996Z", + "timestamp": "2026-03-02T14:46:13.094Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index 3def3f7d4eb..4902540cf3f 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-02-23T16:47:14.066Z", + "timestamp": "2026-03-02T14:46:13.172Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index 6a52ca53499..c26728473ea 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-02-23T16:47:14.187Z", + "timestamp": "2026-03-02T14:46:18.010Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 077ee35fa36..de208d4fa21 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-02-23T16:47:14.188Z", + "timestamp": "2026-03-02T14:46:18.011Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index 2ae5d926cdc..a8a8be09eb6 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-02-23T16:47:14.524Z", + "timestamp": "2026-03-02T14:46:18.324Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index ccf35b682bb..71d1adacb65 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-02-23T16:47:14.873Z", + "timestamp": "2026-03-02T14:46:18.645Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 7677600a53e..492cc4a9c29 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-02-23T16:47:14.873Z", + "timestamp": "2026-03-02T14:46:18.645Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index 9bc73add65e..9ff99596a87 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-02-23T16:47:14.886Z", + "timestamp": "2026-03-02T14:46:18.662Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 2a8011b6b1f..3d5c7c23945 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://weborama.com/deviceStorage.json": { - "timestamp": "2026-02-23T16:47:15.183Z", + "timestamp": "2026-03-02T14:46:18.964Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index 8d10a15c1c2..f6fe924992e 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-02-23T16:47:15.439Z", + "timestamp": "2026-03-02T14:46:19.465Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index 749e127536f..214ac134bbe 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-02-23T16:47:15.869Z", + "timestamp": "2026-03-02T14:46:19.840Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yaleoBidAdapter.json b/metadata/modules/yaleoBidAdapter.json index dd76827e542..b78e431d9c6 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-02-23T16:47:15.869Z", + "timestamp": "2026-03-02T14:46:19.841Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index 6f6e9c3db9c..52962311fa8 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-02-23T16:47:15.870Z", + "timestamp": "2026-03-02T14:46:19.841Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index 81f282fcb98..e0f3e660ae6 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-02-23T16:47:15.998Z", + "timestamp": "2026-03-02T14:46:20.279Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index 03d2e47b63e..ce311353d52 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-02-23T16:47:16.022Z", + "timestamp": "2026-03-02T14:46:20.302Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index 9f3ad140787..3f9dfb1b424 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-02-23T16:47:16.089Z", + "timestamp": "2026-03-02T14:46:20.390Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index 291c37e9782..f215e1e0463 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-02-23T16:47:16.223Z", + "timestamp": "2026-03-02T14:46:20.525Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index 11d006cca2a..517fd76fb4c 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-02-23T16:47:16.313Z", + "timestamp": "2026-03-02T14:46:20.656Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index 83ad693982d..74f3683d12c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.27.0-pre", + "version": "10.27.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.27.0-pre", + "version": "10.27.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", @@ -7869,9 +7869,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", "funding": [ { "type": "opencollective", @@ -27298,9 +27298,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==" + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==" }, "chai": { "version": "4.4.1", diff --git a/package.json b/package.json index daf5b472e5c..901ff570041 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.27.0-pre", + "version": "10.27.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From f096787007a5a33b7e231c7e91f7f9192eb142aa Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 2 Mar 2026 14:53:46 +0000 Subject: [PATCH 104/105] Increment version to 10.28.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 74f3683d12c..85fc646f8bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.27.0", + "version": "10.28.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.27.0", + "version": "10.28.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 901ff570041..c82ac4ff4f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.27.0", + "version": "10.28.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 5fb113e1d49002ef85eaf3b125ea1eefdd021365 Mon Sep 17 00:00:00 2001 From: Roni Shefi Date: Mon, 2 Mar 2026 17:56:59 +0200 Subject: [PATCH 105/105] updated media type resolving, removed some redundant code that's default behavior in prebid --- modules/taboolaBidAdapter.js | 48 +++++---------------- test/spec/modules/taboolaBidAdapter_spec.js | 1 + 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index b7b6c984be1..2cc43a8e925 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -174,7 +174,7 @@ const converter = ortbConverter({ }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); - fillTaboolaImpData(bidRequest, imp); + fillTaboolaImpData(bidRequest, imp, context); return imp; }, request(buildRequest, imps, bidderRequest, context) { @@ -187,9 +187,6 @@ const converter = ortbConverter({ context.mediaType = NATIVE; } else if (bid.mtype === 1) { context.mediaType = BANNER; - } else { - const { mediaType } = getMediaType(context.bidRequest); - context.mediaType = mediaType; } if (context.mediaType === NATIVE) { @@ -234,20 +231,16 @@ export const spec = { validBidRequests.forEach(bid => { const { hasBanner, hasNative } = getMediaType(bid); - if (hasBanner) { - bannerBids.push(hasNative ? {...bid, mediaTypes: {banner: bid.mediaTypes.banner}, nativeOrtbRequest: undefined} : bid); - } - if (hasNative) { - nativeBids.push(hasBanner ? {...bid, mediaTypes: {native: bid.mediaTypes.native}, nativeOrtbRequest: bid.nativeOrtbRequest} : bid); - } + if (hasBanner) bannerBids.push(bid); + if (hasNative) nativeBids.push(bid); }); const requests = []; if (bannerBids.length) { - requests.push(createTaboolaRequest(bannerBids, bidderRequest, BANNER_ENDPOINT_URL)); + requests.push(createTaboolaRequest(bannerBids, bidderRequest, BANNER_ENDPOINT_URL, BANNER)); } if (nativeBids.length) { - requests.push(createTaboolaRequest(nativeBids, bidderRequest, NATIVE_ENDPOINT_URL)); + requests.push(createTaboolaRequest(nativeBids, bidderRequest, NATIVE_ENDPOINT_URL, NATIVE)); } return requests; }, @@ -370,13 +363,13 @@ export const spec = { }, }; -function createTaboolaRequest(bidRequests, bidderRequest, endpointUrl) { +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 } + context: { auctionId, mediaType } }); const {publisherId} = bidRequest.params; const url = endpointUrl + '?publisher=' + publisherId; @@ -477,18 +470,17 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data, context) { } } -function fillTaboolaImpData(bid, imp) { +function fillTaboolaImpData(bid, imp, context) { const {tagId, position} = bid.params; - const { mediaType, hasBanner } = getMediaType(bid); - if (hasBanner) { - imp.banner = getBanners(bid.mediaTypes.banner.sizes, position); + if (imp.banner && position) { + imp.banner.pos = position; } imp.tagid = tagId; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ currency: CURRENCY, - mediaType: mediaType, + mediaType: context.mediaType, size: '*' }); if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { @@ -526,24 +518,6 @@ function fillTaboolaImpData(bid, imp) { } } -function getBanners(sizes, pos) { - return { - ...getSizes(sizes), - pos: pos - } -} - -function getSizes(sizes) { - return { - format: sizes.map(size => { - return { - w: size[0], - h: size[1] - } - }) - } -} - function getMediaType(bidRequest) { const hasBanner = !!bidRequest?.mediaTypes?.banner?.sizes; const hasNative = !!bidRequest?.mediaTypes?.native; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 45c4f26ce49..817757c9f1d 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -275,6 +275,7 @@ describe('Taboola Adapter', function () { 'imp': [{ 'id': res.data.imp[0].id, 'banner': { + topframe: 0, format: [{ w: displayBidRequestParams.mediaTypes.banner.sizes[0][0], h: displayBidRequestParams.mediaTypes.banner.sizes[0][1]