From 4ad039e32fa43c0a8416256d0f8b2a00a2eeed1b Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 9 Oct 2025 05:16:21 -0700 Subject: [PATCH 001/147] Build system: reduce number of e2e test retries (#13993) --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5415d9e678..690bee6a5eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,8 +135,8 @@ jobs: - name: Run tests uses: nick-fields/retry@v3 with: - timeout_minutes: 10 - max_attempts: 3 + timeout_minutes: 20 + max_attempts: 1 command: npx gulp e2e-test coveralls: From abe2502f2fc587c8b1fedb9a9c0d5f6c3a6c668d Mon Sep 17 00:00:00 2001 From: bTimor Date: Thu, 9 Oct 2025 16:06:47 +0300 Subject: [PATCH 002/147] Scalibur Bid Adapter : initial release (#13826) * Add Scalibur bid adapter with comprehensive tests * finish scalibur bid adapter and tests * fix * fix * add missing params * convert response price to cpm * size to valid format * fix tests * remove all uses of first party data and Math.random * fix lint error * fix some more lint errors * add support to own fpd * vlaidate tests and lint * re tests * rerun tests * remove the use of navigator --------- Co-authored-by: Timor Bibi --- modules/scaliburBidAdapter.js | 241 +++++++++++++ modules/scaliburBidAdapter.md | 91 +++++ test/spec/modules/scaliburBidAdapter_spec.js | 340 +++++++++++++++++++ 3 files changed, 672 insertions(+) create mode 100644 modules/scaliburBidAdapter.js create mode 100644 modules/scaliburBidAdapter.md create mode 100644 test/spec/modules/scaliburBidAdapter_spec.js diff --git a/modules/scaliburBidAdapter.js b/modules/scaliburBidAdapter.js new file mode 100644 index 00000000000..b8b05c3ac61 --- /dev/null +++ b/modules/scaliburBidAdapter.js @@ -0,0 +1,241 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {sizesToSizeTuples} from "../src/utils.js"; + +const BIDDER_CODE = 'scalibur'; +const ENDPOINT_SERVER = new URLSearchParams(window.location.search).get('sclServer') || 'srv'; +const ENDPOINT_URL = `https://${ENDPOINT_SERVER}.scalibur.io/adserver/ortb?type=prebid`; +const SYNC_IFRAME_URL = `https://${ENDPOINT_SERVER}.scalibur.io/adserver/sync`; +const SYNC_PIXEL_URL = `https://${ENDPOINT_SERVER}.scalibur.io/adserver/sync`; +const DEFAULT_CURRENCY = 'USD'; +const BIDDER_VERSION = '1.0.0'; +const IFRAME_TYPE_Q_PARAM = 'iframe'; +const IMAGE_TYPE_Q_PARAM = 'img'; +const GVLID = 1471; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const STORAGE_KEY = `${BIDDER_CODE}_fp_data`; + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.placementId); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const ortb2 = bidderRequest.ortb2 || {}; + const ortb2Site = ortb2.site || {}; + const ortb2User = ortb2.user || {}; + const ortb2Regs = ortb2.regs || {}; + const ortb2Device = ortb2.device || {}; + const ortb2SourceExt = ortb2.source?.ext || {}; + const eids = ortb2User?.ext?.eids || []; + const fpd = getFirstPartyData(); + + const payload = { + id: bidderRequest.auctionId, + imp: validBidRequests.map((bid) => { + const imp = { + id: bid.bidId, + ext: { + placementId: bid.params.placementId, + adUnitCode: bid.adUnitCode, + ...bid.params, + }, + }; + + // Banner Media Type + if (bid.mediaTypes.banner) { + imp.banner = { + format: sizesToSizeTuples(bid.mediaTypes.banner.sizes).map((size) => ({ + w: size[0], + h: size[1], + })), + }; + } + + // Video Media Type + if (bid.mediaTypes.video) { + const video = bid.mediaTypes.video; + imp.video = { + mimes: video.mimes || ['video/mp4'], + minduration: video.minduration || 1, + maxduration: video.maxduration || 180, + maxbitrate: video.maxbitrate || 30000, + protocols: video.protocols || [2, 3, 5, 6], + w: video.playerSize?.[0]?.[0] || 640, + h: video.playerSize?.[0]?.[1] || 480, + placement: video.placement || 1, + plcmt: video.plcmt || 1, + skip: video.skip || 0, + skipafter: video.skipafter || 5, + startdelay: video.startdelay || 0, + playbackmethod: video.playbackmethod || [1, 2], + api: video.api || [1, 2], + linearity: video.linearity || 1, + }; + + // OMID Params + if (video.api && video.api.includes(7)) { + if (ortb2SourceExt.omidpn) { + imp.video.omidpn = ortb2SourceExt.omidpn; + } + if (ortb2SourceExt.omidpv) { + imp.video.omidpv = ortb2SourceExt.omidpv; + } + } + } + + // Floor Price + const floor = bid.getFloor ? bid.getFloor({currency: DEFAULT_CURRENCY, mediaType: '*', size: '*'}) : {}; + imp.bidfloor = floor.floor || bid.params.bidfloor || 0; + imp.bidfloorcur = floor.currency || bid.params.bidfloorcur || DEFAULT_CURRENCY; + + // GPID + if (bid.ortb2Imp?.ext?.gpid) { + imp.ext.gpid = bid.ortb2Imp.ext.gpid; + } + + return imp; + }), + site: { + page: bidderRequest.refererInfo.page, + domain: bidderRequest.refererInfo.domain, + ref: ortb2Site.ref || '', + keywords: ortb2Site.keywords || '', + pagecat: ortb2Site.pagecat || [], + content: ortb2Site.content || {}, + }, + device: { + ua: ortb2Device.ua, + language: ortb2Device.language, + sua: ortb2Device.sua || {}, + dnt: ortb2Device.dnt ?? 0, + }, + user: { + eids, + consent: bidderRequest.gdprConsent?.consentString || '', + data: ortb2User.data || [], + }, + regs: { + coppa: ortb2Regs.coppa || 0, + gdpr: bidderRequest.gdprConsent?.gdprApplies ? 1 : 0, + us_privacy: bidderRequest.uspConsent || '', + gpp: bidderRequest.gppConsent?.gppString || '', + gpp_sid: bidderRequest.gppConsent?.applicableSections || [], + ext: { + gpc: ortb2Regs.ext?.gpc || '', + }, + }, + source: { + tid: bidderRequest.auctionId, + }, + tmax: bidderRequest.timeout, + ext: { + prebidVersion: '$prebid.version$', + bidderVersion: BIDDER_VERSION, + isDebug: config.getConfig('debug'), + ...fpd + } + }; + + // Supply Chain + if (validBidRequests[0]?.ortb2?.source?.ext?.schain) { + payload.source.schain = validBidRequests[0]?.ortb2?.source?.ext?.schain; + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }, + + interpretResponse: function (serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + + const bidResponses = []; + const response = serverResponse.body; + + if (response && response.seatbid) { + response.seatbid.forEach((seat) => { + seat.bid.forEach((bid) => { + const imp = request.data.imp.find((i) => i.id === bid.impid); + let bidRes = { + requestId: bid.impid, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.crid || '', + currency: response.cur || DEFAULT_CURRENCY, + netRevenue: true, + ttl: bid.exp || 300, + }; + if (imp && imp.banner) { + bidRes.ad = bid.adm; + bidRes.mediaType = BANNER; + } else if (imp && imp.video) { + bidRes.vastXml = bid.vastXml; + bidRes.vastUrl = bid.vastUrl; + bidRes.mediaType = VIDEO; + } + + bidResponses.push(bidRes); + }); + }); + } + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const gdpr = gdprConsent?.gdprApplies ? 1 : 0; + const gdprConsentString = gdprConsent?.consentString || ''; + const gpp = gdprConsent?.gppString || ''; + const gppSid = gdprConsent?.applicableSections || []; + const usPrivacy = uspConsent || ''; + + const queryParams = [ + `type=${syncOptions.iframeEnabled ? IFRAME_TYPE_Q_PARAM : (syncOptions.pixelEnabled ? IMAGE_TYPE_Q_PARAM : '')}`, + `gdpr=${gdpr}`, + `gdpr_consent=${encodeURIComponent(gdprConsentString)}`, + `us_privacy=${encodeURIComponent(usPrivacy)}`, + `gpp=${encodeURIComponent(gpp)}`, + `gpp_sid=${encodeURIComponent(gppSid.join(','))}`, + ].join('&'); + + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({type: 'iframe', url: `${SYNC_IFRAME_URL}?${queryParams}`}); + } + if (syncOptions.pixelEnabled) { + syncs.push({type: 'image', url: `${SYNC_PIXEL_URL}?${queryParams}`}); + } + return syncs; + }, +}; + +// Also, export storage for easier testing. +export { storage }; + +export function getFirstPartyData() { + if (!storage.hasLocalStorage()) return; + + let rawData = storage.getDataFromLocalStorage(STORAGE_KEY); + let fdata = null; + if (rawData) { + try { + fdata = JSON.parse(rawData); + } catch (e) {} + } + + return fdata || {}; +} + +registerBidder(spec); diff --git a/modules/scaliburBidAdapter.md b/modules/scaliburBidAdapter.md new file mode 100644 index 00000000000..1ad7b5613b7 --- /dev/null +++ b/modules/scaliburBidAdapter.md @@ -0,0 +1,91 @@ +# Overview + +**Module Name**: Scalibur Bid Adapter +**Module Type**: Bidder Adapter +**Maintainer**: support@scalibur.io +**GVLID**: 1471 + +# Description + +The Scalibur Bid Adapter connects publishers to Scalibur's programmatic advertising platform. It supports both banner and video ad formats through OpenRTB 2.x protocol and provides full compliance with privacy regulations. + +**Key Features:** +- Banner and Video ad support +- OpenRTB 2.x compliant +- Privacy regulation compliance +- Floor pricing support +- User sync capabilities +- Supply chain transparency + +# Test Parameters + +## Banner + +```javascript +var adUnits = [ + { + code: 'test-banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + bids: [ + { + bidder: 'scalibur', + params: { + placementId: 'test-scl-placement' // Required + } + } + ] + } +]; +``` + +## Video + +```javascript +var adUnits = [ + { + code: 'test-video-div', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream', + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6], + minduration: 5, + maxduration: 30, + startdelay: 0, + playbackmethod: [1, 2], + api: [1, 2] + } + }, + bids: [ + { + bidder: 'scalibur', + params: { + placementId: 'test-scl-placement' // Required + } + } + ] + } +]; +``` + +# Configuration +## Required Parameters + +| Name | Scope | Description | Example | Type | +| --- | --- | --- | --- | --- | +| `placementId` | required | Placement identifier provided by Scalibur | `'test-placement-123'` | `string` | + +# Additional Information + +## User Syncs +The adapter supports both iframe and image-based user syncs: +- **Iframe sync** +- **Image sync** + +All privacy parameters are automatically included in sync URLs. + diff --git a/test/spec/modules/scaliburBidAdapter_spec.js b/test/spec/modules/scaliburBidAdapter_spec.js new file mode 100644 index 00000000000..c2f6a3c65a8 --- /dev/null +++ b/test/spec/modules/scaliburBidAdapter_spec.js @@ -0,0 +1,340 @@ +import {expect} from 'chai'; +import {spec, getFirstPartyData, storage} from 'modules/scaliburBidAdapter.js'; + +describe('Scalibur Adapter', function () { + const BID = { + 'bidId': 'ec675add-d1d2-4bdd', + 'adUnitCode': '63540ad1df6f42d168cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'params': { + "placementId": "test-scl-placement", + "adUnitCode": "123", + "gpid": "/1234/5678/homepage", + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + "playerSize": [[300, 169]], + "mimes": [ + "video/mp4", + "application/javascript", + "video/webm" + ], + "protocols": [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16], + "api": [1, 2, 7, 8, 9], + 'maxduration': 30, + 'minduration': 15, + 'startdelay': 0, + 'linearity': 1, + 'placement': 1, + "skip": 1, + "skipafter": 5, + }, + 'banner': { + 'sizes': [[300, 250], [728, 90]], + }, + }, + "ortb2Imp": { + "ext": { + "gpid": '/1234/5678/homepage', + }, + }, + }; + + const BIDDER_REQUEST = { + auctionId: 'auction-45678', + refererInfo: { + page: 'https://example-publisher.com', + domain: 'example-publisher.com', + }, + gdprConsent: { + gdprApplies: true, + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + }, + uspConsent: '1---', + ortb2: { + site: { + pagecat: ['IAB1-1', 'IAB3-2'], + ref: 'https://example-referrer.com', + }, + user: { + data: [{name: 'segments', segment: ['sports', 'entertainment']}], + }, + regs: { + ext: { + gpc: '1', + }, + }, + }, + timeout: 3000, + }; + + const DEFAULTS_BID = { + 'bidId': 'ec675add-f23d-4bdd', + 'adUnitCode': '63540ad1df6f42d168cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'params': { + "placementId": "test-scl-placement", + "adUnitCode": "123", + "gpid": "/1234/5678/homepage", + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'minduration': 15, + 'startdelay': 0, + }, + 'banner': { + 'sizes': [[300, 250], [728, 90]], + }, + }, + "ortb2Imp": { + "ext": { + "gpid": '/1234/5678/homepage', + }, + }, + }; + + const DEFAULTS_BIDDER_REQUEST = { + auctionId: 'auction-45633', + refererInfo: { + page: 'https://example-publisher.com', + domain: 'example-publisher.com', + }, + timeout: 3000, + }; + + describe('isBidRequestValid', function () { + it('should return true for valid bid params', function () { + expect(spec.isBidRequestValid(BID)).to.equal(true); + }); + + it('should return false for missing placementId', function () { + const invalidBid = {...BID, params: {}}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should build a valid OpenRTB request', function () { + const request = spec.buildRequests([BID], BIDDER_REQUEST); + const payload = request.data; + + expect(payload.id).to.equal('auction-45678'); + expect(payload.imp).to.have.length(1); + + const imp = payload.imp[0]; + expect(imp.id).to.equal('ec675add-d1d2-4bdd'); + expect(imp.ext.placementId).to.equal('test-scl-placement'); + expect(imp.ext.gpid).to.equal('/1234/5678/homepage'); + expect(imp.banner.format).to.deep.equal([ + {w: 300, h: 250}, + {w: 728, h: 90}, + ]); + + const video = imp.video; + expect(video).to.exist; + expect(video.mimes).to.include('video/mp4'); + expect(video.w).to.equal(300); + expect(video.h).to.equal(169); + expect(video.placement).to.equal(1); + expect(video.plcmt).to.equal(1); + expect(video.api).to.include(7); + expect(payload.regs.ext.gpc).to.equal('1'); + }); + }); + + describe('buildRequests', function () { + it('should build a valid OpenRTB request with default values', function () { + const request = spec.buildRequests([DEFAULTS_BID], DEFAULTS_BIDDER_REQUEST); + const payload = request.data; + + expect(payload.id).to.equal('auction-45633'); + expect(payload.imp).to.have.length(1); + + const imp = payload.imp[0]; + expect(imp.id).to.equal('ec675add-f23d-4bdd'); + expect(imp.ext.placementId).to.equal('test-scl-placement'); + expect(imp.ext.gpid).to.equal('/1234/5678/homepage'); + expect(imp.banner.format).to.deep.equal([ + {w: 300, h: 250}, + {w: 728, h: 90}, + ]); + + const video = imp.video; + expect(video).to.exist; + expect(video.mimes).to.deep.equal(['video/mp4']); + expect(video.maxduration).to.equal(180); + expect(video.w).to.equal(640); + expect(video.h).to.equal(480); + expect(video.placement).to.equal(1); + expect(video.skip).to.equal(0); + expect(video.skipafter).to.equal(5); + expect(video.api).to.deep.equal([1, 2]); + expect(video.linearity).to.equal(1); + expect(payload.site.ref).to.equal(''); + expect(payload.site.pagecat).to.deep.equal([]); + expect(payload.user.consent).to.equal(''); + expect(payload.user.data).to.deep.equal([]); + expect(payload.regs.gdpr).to.equal(0); + expect(payload.regs.us_privacy).to.equal(''); + expect(payload.regs.ext.gpc).to.equal(''); + }); + }); + + describe('interpretResponse', function () { + it('should interpret server response correctly', function () { + const serverResponse = { + body: { + seatbid: [ + { + bid: [ + { + impid: '1', + cpm: 2.5, + width: 300, + height: 250, + crid: 'creative-23456', + adm: '
Sample Ad Markup
', + cur: 'USD', + }, + ], + }, + ], + }, + }; + const request = spec.buildRequests([BID], BIDDER_REQUEST); + const bidResponses = spec.interpretResponse(serverResponse, request); + + expect(bidResponses).to.have.length(1); + + const response = bidResponses[0]; + expect(response.requestId).to.equal('1'); + expect(response.cpm).to.equal(2.5); + expect(response.width).to.equal(300); + expect(response.height).to.equal(250); + expect(response.creativeId).to.equal('creative-23456'); + expect(response.currency).to.equal('USD'); + expect(response.netRevenue).to.equal(true); + expect(response.ttl).to.equal(300); + }); + }); + + describe('getUserSyncs', function () { + it('should return iframe and pixel sync URLs with correct params', function () { + const syncOptions = {iframeEnabled: true, pixelEnabled: true}; + const gdprConsent = { + gdprApplies: true, + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + }; + const uspConsent = '1---'; + + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent); + + expect(syncs).to.have.length(2); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'); + expect(syncs[0].url).to.include('us_privacy=1---'); + expect(syncs[1].type).to.equal('image'); + }); + }); + + describe('getScaliburFirstPartyData', function () { + let sandbox; + let storageStub; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + storageStub = { + hasLocalStorage: sandbox.stub(), + getDataFromLocalStorage: sandbox.stub() + }; + + // Replace storage methods + sandbox.stub(storage, 'hasLocalStorage').callsFake(storageStub.hasLocalStorage); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake(storageStub.getDataFromLocalStorage); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should return undefined when localStorage is not available', function () { + storageStub.hasLocalStorage.returns(false); + + const result = getFirstPartyData(); + + expect(result).to.be.undefined; + expect(storageStub.getDataFromLocalStorage.called).to.be.false; + }); + + it('should return existing first party data when available', function () { + const existingData = { + pcid: 'existing-uuid-1234-5678-abcd-ef1234567890', + pcidDate: 1640995200000 + }; + + storageStub.hasLocalStorage.returns(true); + storageStub.getDataFromLocalStorage.returns(JSON.stringify(existingData)); + + const result = getFirstPartyData(); + + // Should use existing data + expect(result.pcid).to.equal(existingData.pcid); + expect(result.pcidDate).to.equal(existingData.pcidDate); + }); + }); + + describe('buildRequests with first party data', function () { + let sandbox; + let storageStub; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + storageStub = { + hasLocalStorage: sandbox.stub(), + getDataFromLocalStorage: sandbox.stub(), + }; + + sandbox.stub(storage, 'hasLocalStorage').callsFake(storageStub.hasLocalStorage); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake(storageStub.getDataFromLocalStorage); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should include first party data in buildRequests when available', function () { + const testData = { + pcid: 'test-uuid-1234-5678-abcd-ef1234567890', + pcidDate: 1640995200000 + }; + + storageStub.hasLocalStorage.returns(true); + storageStub.getDataFromLocalStorage.returns(JSON.stringify(testData)); + + const request = spec.buildRequests([DEFAULTS_BID], DEFAULTS_BIDDER_REQUEST); + + expect(request.data.ext.pcid).to.equal(testData.pcid); + expect(request.data.ext.pcidDate).to.equal(testData.pcidDate); + }); + + it('should not include first party data when localStorage is unavailable', function () { + storageStub.hasLocalStorage.returns(false); + + const request = spec.buildRequests([DEFAULTS_BID], DEFAULTS_BIDDER_REQUEST); + + expect(request.data.ext.pcid).to.be.undefined; + expect(request.data.ext.pcidDate).to.be.undefined; + }); + }); +}); From 3d76c96222c6a603d78662af4c6fd95a56496971 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 9 Oct 2025 06:08:37 -0700 Subject: [PATCH 003/147] Core: refactor window dimensions utilities to only access DOM APIs when necessary (#13929) * cachedApiWrapper * lazy access target * make parent available to getTarget * simplify * consolidate extraWinDimensions * Remove sketchy dimensions * adnuntius: remove availWidth/availHeight * onetag: stop using outer & avail dims --------- Co-authored-by: Patrick McCann --- .../extraWinDimensions/extraWinDimensions.js | 27 ------- modules/adnuntiusBidAdapter.js | 5 -- modules/datablocksBidAdapter.js | 8 +- src/utils/cachedApiWrapper.js | 26 +++++++ src/utils/winDimensions.js | 78 ++++++++----------- .../spec/libraries/extraWinDimensions_spec.js | 19 ----- test/spec/modules/adnuntiusBidAdapter_spec.js | 28 +++---- test/spec/utils/cachedApiWrapper_spec.js | 57 ++++++++++++++ test/spec/utils_spec.js | 14 ++-- 9 files changed, 138 insertions(+), 124 deletions(-) delete mode 100644 libraries/extraWinDimensions/extraWinDimensions.js create mode 100644 src/utils/cachedApiWrapper.js delete mode 100644 test/spec/libraries/extraWinDimensions_spec.js create mode 100644 test/spec/utils/cachedApiWrapper_spec.js diff --git a/libraries/extraWinDimensions/extraWinDimensions.js b/libraries/extraWinDimensions/extraWinDimensions.js deleted file mode 100644 index 5026a08b737..00000000000 --- a/libraries/extraWinDimensions/extraWinDimensions.js +++ /dev/null @@ -1,27 +0,0 @@ -import {canAccessWindowTop, internal as utilsInternals} from '../../src/utils.js'; -import {cachedGetter, internal as dimInternals} from '../../src/utils/winDimensions.js'; - -export const internal = { - fetchExtraDimensions -}; - -const extraDims = cachedGetter(() => internal.fetchExtraDimensions()); -/** - * Using these dimensions may flag you as a fingerprinting tool - * cfr. https://github.com/duckduckgo/tracker-radar/blob/main/build-data/generated/api_fingerprint_weights.json - */ -export const getExtraWinDimensions = extraDims.get; -dimInternals.resetters.push(extraDims.reset); - -function fetchExtraDimensions() { - const win = canAccessWindowTop() ? utilsInternals.getWindowTop() : utilsInternals.getWindowSelf(); - return { - outerWidth: win.outerWidth, - outerHeight: win.outerHeight, - screen: { - availWidth: win.screen?.availWidth, - availHeight: win.screen?.availHeight, - colorDepth: win.screen?.colorDepth, - } - }; -} diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 17b335d3cbe..4a4556cfb1e 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -14,7 +14,6 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {toLegacyResponse, toOrtbNativeRequest} from '../src/native.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import {getExtraWinDimensions} from '../libraries/extraWinDimensions/extraWinDimensions.js'; const BIDDER_CODE = 'adnuntius'; const BIDDER_CODE_DEAL_ALIAS_BASE = 'adndeal'; @@ -304,10 +303,6 @@ export const spec = { queryParamsAndValues.push('consentString=' + consentString); queryParamsAndValues.push('gdpr=' + flag); } - const extraDims = getExtraWinDimensions(); - if (extraDims.screen.availHeight) { - queryParamsAndValues.push('screen=' + extraDims.screen.availWidth + 'x' + extraDims.screen.availHeight); - } const { innerWidth, innerHeight } = getWinDimensions(); diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 27e91028427..60ad1ffbcea 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -6,7 +6,6 @@ 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 {getExtraWinDimensions} from '../libraries/extraWinDimensions/extraWinDimensions.js'; import {isWebdriverEnabled} from '../libraries/webdriver/webdriver.js'; export const storage = getStorageManager({bidderCode: 'datablocks'}); @@ -206,13 +205,12 @@ export const spec = { const botTest = new BotClientTests(); const win = getWindowTop(); const windowDimensions = getWinDimensions(); - const extraDims = getExtraWinDimensions(); return { 'wiw': windowDimensions.innerWidth, 'wih': windowDimensions.innerHeight, - 'saw': extraDims.screen.availWidth, - 'sah': extraDims.screen.availHeight, - 'scd': extraDims.screen.colorDepth, + 'saw': windowDimensions.screen.availWidth, + 'sah': windowDimensions.screen.availHeight, + 'scd': windowDimensions.screen.colorDepth, 'sw': windowDimensions.screen.width, 'sh': windowDimensions.screen.height, 'whl': win.history.length, diff --git a/src/utils/cachedApiWrapper.js b/src/utils/cachedApiWrapper.js new file mode 100644 index 00000000000..9939f23df17 --- /dev/null +++ b/src/utils/cachedApiWrapper.js @@ -0,0 +1,26 @@ +export function CachedApiWrapper(getTarget, props) { + const wrapper = {}; + let data = {}; + const children = []; + Object.entries(props).forEach(([key, value]) => { + if (value != null && typeof value === 'object') { + const child = new CachedApiWrapper(() => getTarget()?.[key], value) + wrapper[key] = child.obj; + children.push(child.reset); + } else if (value === true) { + Object.defineProperty(wrapper, key, { + get() { + if (!data.hasOwnProperty(key)) { + data[key] = getTarget()?.[key]; + } + return data[key]; + } + }) + } + }) + this.obj = wrapper; + this.reset = function () { + children.forEach(reset => reset()); + data = {}; + }; +} diff --git a/src/utils/winDimensions.js b/src/utils/winDimensions.js index 2773ffc1721..01122a9761c 100644 --- a/src/utils/winDimensions.js +++ b/src/utils/winDimensions.js @@ -1,65 +1,55 @@ import {canAccessWindowTop, internal as utilsInternals} from '../utils.js'; +import {CachedApiWrapper} from './cachedApiWrapper.js'; const CHECK_INTERVAL_MS = 20; -export function cachedGetter(getter) { - let value, lastCheckTimestamp; - return { - get: function () { - if (!value || !lastCheckTimestamp || (Date.now() - lastCheckTimestamp > CHECK_INTERVAL_MS)) { - value = getter(); - lastCheckTimestamp = Date.now(); - } - return value; - }, - reset: function () { - value = getter(); - } - } -} - -function fetchWinDimensions() { - const top = canAccessWindowTop() ? utilsInternals.getWindowTop() : utilsInternals.getWindowSelf(); - - return { +const winDimensions = new CachedApiWrapper( + () => canAccessWindowTop() ? utilsInternals.getWindowTop() : utilsInternals.getWindowSelf(), + { + innerHeight: true, + innerWidth: true, screen: { - width: top.screen?.width, - height: top.screen?.height + width: true, + height: true, }, - innerHeight: top.innerHeight, - innerWidth: top.innerWidth, visualViewport: { - height: top.visualViewport?.height, - width: top.visualViewport?.width, + width: true, + height: true }, document: { documentElement: { - clientWidth: top.document?.documentElement?.clientWidth, - clientHeight: top.document?.documentElement?.clientHeight, - scrollTop: top.document?.documentElement?.scrollTop, - scrollLeft: top.document?.documentElement?.scrollLeft, + clientWidth: true, + clientHeight: true, + scrollTop: true, + scrollLeft: true }, body: { - scrollTop: document.body?.scrollTop, - scrollLeft: document.body?.scrollLeft, - clientWidth: document.body?.clientWidth, - clientHeight: document.body?.clientHeight, - }, + scrollTop: true, + scrollLeft: true, + clientWidth: true, + clientHeight: true + } } - }; -} + } +); + export const internal = { - fetchWinDimensions, - resetters: [] + reset: winDimensions.reset, }; -const winDimensions = cachedGetter(() => internal.fetchWinDimensions()); - -export const getWinDimensions = winDimensions.get; -internal.resetters.push(winDimensions.reset); +export const getWinDimensions = (() => { + let lastCheckTimestamp; + return function () { + if (!lastCheckTimestamp || (Date.now() - lastCheckTimestamp > CHECK_INTERVAL_MS)) { + internal.reset(); + lastCheckTimestamp = Date.now(); + } + return winDimensions.obj; + } +})(); export function resetWinDimensions() { - internal.resetters.forEach(fn => fn()); + internal.reset(); } export function getScreenOrientation(win) { diff --git a/test/spec/libraries/extraWinDimensions_spec.js b/test/spec/libraries/extraWinDimensions_spec.js deleted file mode 100644 index 32c8a35a95f..00000000000 --- a/test/spec/libraries/extraWinDimensions_spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import {resetWinDimensions} from '../../../src/utils.js'; -import * as extraWinDimensions from '../../../libraries/extraWinDimensions/extraWinDimensions.js'; - -describe('extraWinDimensions', () => { - let sandbox; - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.stub() - }) - - it('should reset together with basic dimensions', () => { - const resetSpy = sinon.spy(extraWinDimensions.internal, 'fetchExtraDimensions'); - resetWinDimensions(); - sinon.assert.called(resetSpy); - }) -}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 47434556426..8a531ba08db 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -1,15 +1,14 @@ -import { expect } from 'chai'; -import { spec } from 'modules/adnuntiusBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import { config } from 'src/config.js'; +import {expect} from 'chai'; +import {spec} from 'modules/adnuntiusBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import { getStorageManager } from 'src/storageManager.js'; -import { getGlobal } from '../../../src/prebidGlobal.js'; import {deepClone, getUnixTimestampFromNow} from 'src/utils.js'; -import { getWinDimensions } from '../../../src/utils.js'; +import {getStorageManager} from 'src/storageManager.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; +import {getWinDimensions} from '../../../src/utils.js'; import {getGlobalVarName} from '../../../src/buildOptions.js'; -import {getExtraWinDimensions} from '../../../libraries/extraWinDimensions/extraWinDimensions.js'; describe('adnuntiusBidAdapter', function () { const sandbox = sinon.createSandbox(); @@ -51,7 +50,6 @@ describe('adnuntiusBidAdapter', function () { const tzo = new Date().getTimezoneOffset(); const prebidVersion = getGlobal().version; - let screen; let viewport; let ENDPOINT_URL_BASE; let ENDPOINT_URL; @@ -62,15 +60,13 @@ describe('adnuntiusBidAdapter', function () { function resetExpectedUrls() { const winDimensions = getWinDimensions(); - const extraDims = getExtraWinDimensions(); - screen = extraDims.screen.availWidth + 'x' + extraDims.screen.availHeight; viewport = winDimensions.innerWidth + 'x' + winDimensions.innerHeight; - ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid&pbv=${prebidVersion}&screen=${screen}&viewport=${viewport}`; + ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid&pbv=${prebidVersion}&viewport=${viewport}`; ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; - LOCALHOST_URL = `http://localhost:8078/i?tzo=${tzo}&format=prebid&pbv=${prebidVersion}&screen=${screen}&viewport=${viewport}&userId=${usi}`; + LOCALHOST_URL = `http://localhost:8078/i?tzo=${tzo}&format=prebid&pbv=${prebidVersion}&viewport=${viewport}&userId=${usi}`; ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; - ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&pbv=${prebidVersion}&consentString=consentString&gdpr=1&screen=${screen}&viewport=${viewport}&userId=${usi}`; + ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&pbv=${prebidVersion}&consentString=consentString&gdpr=1&viewport=${viewport}&userId=${usi}`; } function expectUrlsEqual(actual, expected) { @@ -888,12 +884,10 @@ describe('adnuntiusBidAdapter', function () { describe('buildRequests', function () { it('Test requests', function () { const winDimensions = getWinDimensions(); - const extraDims = getExtraWinDimensions(); - const screen = extraDims.screen.availWidth + 'x' + extraDims.screen.availHeight; const viewport = winDimensions.innerWidth + 'x' + winDimensions.innerHeight; const prebidVersion = window[getGlobalVarName()].version; const tzo = new Date().getTimezoneOffset(); - const ENDPOINT_URL = `https://ads.adnuntius.delivery/i?tzo=${tzo}&format=prebid&pbv=${prebidVersion}&screen=${screen}&viewport=${viewport}&userId=${usi}`; + const ENDPOINT_URL = `https://ads.adnuntius.delivery/i?tzo=${tzo}&format=prebid&pbv=${prebidVersion}&viewport=${viewport}&userId=${usi}`; const bidderRequests = [ { diff --git a/test/spec/utils/cachedApiWrapper_spec.js b/test/spec/utils/cachedApiWrapper_spec.js new file mode 100644 index 00000000000..b354ed304b7 --- /dev/null +++ b/test/spec/utils/cachedApiWrapper_spec.js @@ -0,0 +1,57 @@ +import {CachedApiWrapper} from '../../../src/utils/cachedApiWrapper.js'; + +describe('cachedApiWrapper', () => { + let target, child, grandchild, wrapper; + beforeEach(() => { + grandchild = {}; + child = { + grandchild + }; + target = { + child + }; + wrapper = new CachedApiWrapper(() => target, { + prop1: true, + child: { + prop2: true, + grandchild: { + prop3: true + } + } + }) + }); + + it('should delegate to target', () => { + target.prop1 = 'value'; + expect(wrapper.obj.prop1).to.eql('value'); + }); + it('should cache result', () => { + target.prop1 = 'value'; + expect(wrapper.obj.prop1).to.eql('value'); + target.prop1 = 'newValue'; + expect(wrapper.obj.prop1).to.eql('value'); + }); + + it('should clear cache on reset', () => { + target.prop1 = 'value'; + expect(wrapper.obj.prop1).to.eql('value'); + target.prop1 = 'newValue'; + wrapper.reset(); + expect(wrapper.obj.prop1).to.eql('newValue'); + }); + + it('should unwrap wrappers in obj', () => { + grandchild.prop3 = 'value'; + expect(wrapper.obj.child.grandchild.prop3).to.eql('value'); + grandchild.prop3 = 'value'; + expect(wrapper.obj.child.grandchild.prop3).to.eql('value'); + }); + + it('should reset childrens cache', () => { + child.prop2 = 'value'; + expect(wrapper.obj.child.prop2).to.eql('value'); + wrapper.reset(); + child.prop2 = 'newValue'; + expect(wrapper.obj.child.prop2).to.eql('newValue'); + }) +}) diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 6db7a3561eb..1efdc5621f6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1445,18 +1445,18 @@ describe('getWinDimensions', () => { clock.restore(); }); - it('should invoke fetchWinDimensions once per 20ms', () => { - const resetWinDimensionsSpy = sinon.spy(winDimensions.internal, 'fetchWinDimensions'); - getWinDimensions(); + it('should clear cache once per 20ms', () => { + const resetWinDimensionsSpy = sinon.spy(winDimensions.internal, 'reset'); + expect(getWinDimensions().innerHeight).to.exist; clock.tick(1); - getWinDimensions(); + expect(getWinDimensions().innerHeight).to.exist; clock.tick(1); - getWinDimensions(); + expect(getWinDimensions().innerHeight).to.exist; clock.tick(1); - getWinDimensions(); + expect(getWinDimensions().innerHeight).to.exist; sinon.assert.calledOnce(resetWinDimensionsSpy); clock.tick(18); - getWinDimensions(); + expect(getWinDimensions().innerHeight).to.exist; sinon.assert.calledTwice(resetWinDimensionsSpy); }); }); From 038af153bf4705c36d2d823481cc38c61204a5b7 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 9 Oct 2025 15:09:06 +0200 Subject: [PATCH 004/147] Core: Adding bidLimit to adUnit (#13930) * Core: targeting bids limit varying on ad unit * handling other case * refactoring * moving bidLimit to adUnit level * fixing sendAllBids issue, allowing adunitBidLimit as object * restoring missing comment * performance optimization --- src/targeting.ts | 33 +++++++++++++++--- test/spec/unit/core/targeting_spec.js | 49 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/targeting.ts b/src/targeting.ts index 4016c5b4bd3..477ddaab7f0 100644 --- a/src/targeting.ts +++ b/src/targeting.ts @@ -73,9 +73,10 @@ export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, const bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode') Object.keys(bidsByBidder).forEach(key => { bucketBids.push(bidsByBidder[key].reduce(winReducer)) }); // if adUnitBidLimit is set, pass top N number bids - if (adUnitBidLimit) { + const bidLimit = typeof adUnitBidLimit === 'object' ? adUnitBidLimit[bucketKey] : adUnitBidLimit; + if (bidLimit) { bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); - bids.push(...bucketBids.slice(0, adUnitBidLimit)); + bids.push(...bucketBids.slice(0, bidLimit)); } else { bucketBids = bucketBids.sort(winSorter) bids.push(...bucketBids); @@ -142,6 +143,30 @@ export function getGPTSlotsForAdUnits(adUnitCodes: AdUnitCode[], customSlotMatch return auToSlots; }, Object.fromEntries(adUnitCodes.map(au => [au, []]))); } +/* * + * Returns a map of adUnitCodes to their bid limits. If sendAllBids is disabled, all adUnits will have a bid limit of 0. + * If sendAllBids is enabled, the bid limit for each adUnit will be determined by the following precedence: + * 1. The bidLimit property of the adUnit object + * 2. The bidLimit parameter passed to this function + * 3. The global sendBidsControl.bidLimit config property + * + * @param adUnitCodes + * @param bidLimit + */ +export function getAdUnitBidLimitMap(adUnitCodes: AdUnitCode[], bidLimit: number): ByAdUnit | number { + if (!config.getConfig('enableSendAllBids')) return 0; + const bidLimitConfigValue = config.getConfig('sendBidsControl.bidLimit'); + const adUnitCodesSet = new Set(adUnitCodes); + + const result: ByAdUnit = {}; + for (const au of auctionManager.getAdUnits()) { + if (adUnitCodesSet.has(au.code)) { + result[au.code] = au?.bidLimit || bidLimit || bidLimitConfigValue; + } + } + + return result; +} export type TargetingMap = Partial & { [targetingKey: string]: V @@ -238,9 +263,7 @@ export function newTargeting(auctionManager) { getAllTargeting(adUnitCode?: AdUnitCode | AdUnitCode[], bidLimit?: number, bidsReceived?: Bid[], winReducer = getHighestCpm, winSorter = sortByHighestCpm): ByAdUnit { bidsReceived ||= getBidsReceived(winReducer, winSorter); const adUnitCodes = getAdUnitCodes(adUnitCode); - const sendAllBids = config.getConfig('enableSendAllBids'); - const bidLimitConfigValue = config.getConfig('sendBidsControl.bidLimit'); - const adUnitBidLimit = (sendAllBids && (bidLimit || bidLimitConfigValue)) || 0; + const adUnitBidLimit = getAdUnitBidLimitMap(adUnitCodes, bidLimit); const { customKeysByUnit, filteredBids } = getfilteredBidsAndCustomKeys(adUnitCodes, bidsReceived); const bidsSorted = getHighestCpmBidsFromBidPool(filteredBids, winReducer, adUnitBidLimit, undefined, winSorter); let targeting = getTargetingLevels(bidsSorted, customKeysByUnit, adUnitCodes); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 72347beae9b..3ce5db91ef2 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -16,6 +16,7 @@ import {createBid} from '../../../../src/bidfactory.js'; import {hook, setupBeforeHookFnOnce} from '../../../../src/hook.js'; import {getHighestCpm} from '../../../../src/utils/reducers.js'; import {getGlobal} from '../../../../src/prebidGlobal.js'; +import { getAdUnitBidLimitMap } from '../../../../src/targeting.js'; function mkBid(bid) { return Object.assign(createBid(), bid); @@ -546,6 +547,12 @@ describe('targeting tests', function () { }); it('selects the top n number of bids when enableSendAllBids is true and and bitLimit is set', function () { + let getAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnits').callsFake(() => ([ + { + code: '/123456/header-bid-tag-0', + }, + ])); + config.setConfig({ sendBidsControl: { bidLimit: 1 @@ -555,6 +562,7 @@ describe('targeting tests', function () { const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); const limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(TARGETING_KEYS.PRICE_BUCKET + '_') !== -1) + getAdUnitsStub.restore(); expect(limitedBids.length).to.equal(1); }); @@ -583,8 +591,49 @@ describe('targeting tests', function () { expect(limitedBids.length).to.equal(2); }); + + it('getHighestCpmBidsFromBidPool calculates bids limit properly when bidLimit is a map', function () { + const bidLimit = { + 'adunit1': 2 + }; + const bids = [ + { ...bid1, bidderCode: 'rubicon', adUnitCode: 'adunit1' }, + { ...bid2, bidderCode: 'appnexus', adUnitCode: 'adunit1' }, + { ...bid3, bidderCode: 'dgads', adUnitCode: 'adunit1' }, + ]; + + const limitedBids = getHighestCpmBidsFromBidPool(bids, getHighestCpm, bidLimit); + + expect(limitedBids.length).to.equal(2); + }); }); + it('getAdUnitBidLimitMap returns correct map of adUnitCode to bidLimit', function() { + enableSendAllBids = true; + let getAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnits').callsFake(() => ([ + { + code: 'adunit1', + bidLimit: 2 + }, + { + code: 'adunit2', + bidLimit: 5 + }, + { + code: 'adunit3' + } + ])); + + const adUnitBidLimitMap = getAdUnitBidLimitMap(['adunit1', 'adunit2', 'adunit3'], 0); + + expect(adUnitBidLimitMap).to.deep.equal({ + 'adunit1': 2, + 'adunit2': 5, + 'adunit3': undefined + }); + getAdUnitsStub.restore(); + }) + describe('targetingControls.allowZeroCpmBids', function () { let bid4; let bidderSettingsStorage; From ba95566289c36773e5b51580e8abcd1113f4c0da Mon Sep 17 00:00:00 2001 From: Andrey Filipov Date: Thu, 9 Oct 2025 16:11:55 +0300 Subject: [PATCH 005/147] Yandex Bid Adapter: Added banner coordinates to the request (#13944) * Yandex Bid Adapter: Added banner coordinates to the request * fix webdriver * Yandex Bid Adapter: Added banner coordinates to the request * Yandex Bid Adapter: Added banner coordinates to the request * Update modules/yandexBidAdapter.js Co-authored-by: Graham Higgins --------- Co-authored-by: Graham Higgins --- modules/yandexBidAdapter.js | 147 ++++++++++++++++++++- test/spec/modules/yandexBidAdapter_spec.js | 134 +++++++++++++++++-- 2 files changed, 269 insertions(+), 12 deletions(-) diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index c0f4550a0ce..c79c55f8462 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -2,7 +2,8 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel, logInfo } from '../src/utils.js'; +import { inIframe, _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel, logInfo } from '../src/utils.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { ajax } from '../src/ajax.js'; import { config as pbjsConfig } from '../src/config.js'; import { isWebdriverEnabled } from '../libraries/webdriver/webdriver.js'; @@ -53,7 +54,7 @@ const BIDDER_CODE = 'yandex'; const BIDDER_URL = 'https://yandex.ru/ads/prebid'; const EVENT_TRACKER_URL = 'https://yandex.ru/ads/trace'; // We send data in 1% of cases -const DEFAULT_SAMPLING_RATE = 0.1; +const DEFAULT_SAMPLING_RATE = 0.01; const EVENT_LOG_RANDOM_NUMBER = Math.random(); const DEFAULT_TTL = 180; const DEFAULT_CURRENCY = 'EUR'; @@ -69,7 +70,7 @@ const ORTB_MTYPES = { }; const SSP_ID = 10500; -const ADAPTER_VERSION = '2.7.0'; +const ADAPTER_VERSION = '2.8.0'; const TRACKER_METHODS = { img: 1, @@ -177,6 +178,14 @@ export const spec = { queryParams['tcf-consent'] = consentString; } + const adUnitElement = document.getElementById(bidRequest.params.pubcontainerid || bidRequest.adUnitCode); + const windowContext = getContext(adUnitElement); + const isIframe = inIframe(); + const coords = isIframe ? getFramePosition() : { + x: adUnitElement && getBoundingClientRect(adUnitElement).x, + y: adUnitElement && getBoundingClientRect(adUnitElement).y, + }; + const imp = { id: impId, banner: mapBanner(bidRequest), @@ -184,6 +193,10 @@ export const spec = { video: mapVideo(bidRequest), displaymanager: 'Prebid.js', displaymanagerver: '$prebid.version$', + ext: { + isvisible: isVisible(adUnitElement), + coords, + } }; const bidfloor = getBidfloor(bidRequest); @@ -223,7 +236,15 @@ export const spec = { deepSetValue(data, 'user.ext.eids', eids); } + deepSetValue(data, 'ext.isiframe', isIframe); + + if (windowContext) { + deepSetValue(data, 'device.ext.scroll.top', windowContext.scrollY); + deepSetValue(data, 'device.ext.scroll.left', windowContext.scrollX); + } + const queryParamsString = formatQS(queryParams); + const request = { method: 'POST', url: BIDDER_URL + `/${pageId}?${queryParamsString}`, @@ -619,4 +640,124 @@ function eventLog(name, resp) { } } +/** + * Determines the appropriate window context for a given DOM element by checking + * its presence in the current window's DOM or the top-level window's DOM. + * + * This is useful for cross-window/frame DOM interactions where security restrictions + * might apply (e.g., same-origin policy). The function safely handles cases where + * cross-window access might throw errors. + * + * @param {Element|null|undefined} elem - The DOM element to check. Can be falsy. + * @returns {Window|undefined} Returns the appropriate window object where the element + * belongs (current window or top window). Returns undefined if the element is not found + * in either context or if access is denied due to cross-origin restrictions. + */ +function getContext(elem) { + try { + // Check if the element exists and is in the current window's DOM + if (elem) { + if (window.document.body.contains(elem)) { + return window; // Element is in current window + } else if (window.top.document.body.contains(elem)) { + return window.top; // Element exists in top window's DOM + } + return undefined; // Element not found in any accessible context} + } + } catch (e) { + // Handle cases where cross-origin access to top window's DOM is blocked + return undefined; + } +} + +/** + * Checks if an element is visible in the DOM + * @param {Element} elem - The element to check for visibility + * @returns {boolean} True if the element is visible, false otherwise + */ +function isVisible(elem) { + // Return false for non-existent elements + if (!elem) { + return false; + } + + // Get the rendering context for the element + const context = getContext(elem); + + // Return false if no context is available (element not in DOM) + if (!context) { + return false; + } + + let currentElement = elem; + let iterations = 0; + const MAX_ITERATIONS = 250; + + // Traverse up the DOM tree to check parent elements + while (currentElement && iterations < MAX_ITERATIONS) { + iterations++; + + try { + // Get computed styles for the current element + const computedStyle = context.getComputedStyle(currentElement); + + // Check for hiding styles: display: none or visibility: hidden + const isHidden = computedStyle.display === 'none' || + computedStyle.visibility === 'hidden'; + + if (isHidden) { + return false; // Element is hidden + } + } catch (error) { + // If we can't access styles, assume element is not visible + return false; + } + + // Move to the parent element for the next iteration + currentElement = currentElement.parentElement; + } + + // If we've reached the root without finding hiding styles, element is visible + return true; +} + +/** + * Calculates the cumulative position of the current frame within nested iframes. + * This is useful when you need the absolute position of an element within nested iframes + * relative to the outermost main window. + * + * @returns {Array} [totalLeft, totalTop] - Cumulative left and top offsets in pixels + */ +function getFramePosition() { + let currentWindow = window; + let iterationCount = 0; + let totalLeft = 0; + let totalTop = 0; + const MAX_ITERATIONS = 100; + + do { + iterationCount++; + + try { + // After first iteration, move to parent window + if (iterationCount > 1) { + currentWindow = currentWindow.parent; + } + + // Get the frame element containing current window and its position + const frameElement = currentWindow.frameElement; + const rect = getBoundingClientRect(frameElement); + + // Accumulate frame element's position offsets + totalLeft += rect.left; + totalTop += rect.top; + } catch (error) { + // Continue processing if we can't access frame element (e.g., cross-origin restriction) + // Error is ignored as we can't recover frame position information in this case + } + } while (iterationCount < MAX_ITERATIONS && currentWindow.parent !== currentWindow.self); + + return { x: totalLeft, y: totalTop }; +} + registerBidder(spec); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index bd0d2157ce0..d0d9bbec263 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -8,9 +8,10 @@ import { BANNER, NATIVE } from '../../../src/mediaTypes.js'; import { addFPDToBidderRequest } from '../../helpers/fpd.js'; import * as webdriver from '../../../libraries/webdriver/webdriver.js'; -describe('Yandex adapter', function () { - let sandbox; +const adUnitCode = 'adUnit-123'; +let sandbox; +describe('Yandex adapter', function () { beforeEach(function () { sandbox = sinon.createSandbox(); @@ -65,12 +66,7 @@ describe('Yandex adapter', function () { let mockBidderRequest; beforeEach(function () { - mockBidRequests = [{ - bidId: 'bid123', - params: { - placementId: 'R-I-123456-2', - } - }]; + mockBidRequests = [getBidRequest()]; mockBidderRequest = { ortb2: { device: { @@ -85,6 +81,15 @@ describe('Yandex adapter', function () { } } }; + + sandbox.stub(frameElement, 'getBoundingClientRect').returns({ + left: 123, + top: 234, + }); + }); + + afterEach(function () { + removeElement(adUnitCode); }); it('should set site.content.language from document language if it is not set', function () { @@ -110,6 +115,37 @@ describe('Yandex adapter', function () { expect(requests[0].data.imp[0].displaymanagerver).to.not.be.undefined; }); + it('should return banner coordinates', function () { + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + expect(requests[0].data.imp[0].ext.coords.x).to.equal(123); + expect(requests[0].data.imp[0].ext.coords.y).to.equal(234); + }); + + it('should return page scroll coordinates', function () { + createElementVisible(adUnitCode); + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + expect(requests[0].data.device.ext.scroll.top).to.equal(0); + expect(requests[0].data.device.ext.scroll.left).to.equal(0); + }); + + it('should return correct visible', function () { + createElementVisible(adUnitCode); + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + expect(requests[0].data.imp[0].ext.isvisible).to.equal(true); + }); + + it('should return correct visible for hidden element', function () { + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + createElementHidden(adUnitCode); + expect(requests[0].data.imp[0].ext.isvisible).to.equal(false); + }); + + it('should return correct visible for invisible element', function () { + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + createElementInvisible(adUnitCode); + expect(requests[0].data.imp[0].ext.isvisible).to.equal(false); + }); + /** @type {import('../../../src/auction').BidderRequest} */ const bidderRequest = { ortb2: { @@ -957,7 +993,87 @@ function getBidRequest(extra = {}) { return { ...getBidConfig(), bidId: 'bidid-1', - adUnitCode: 'adUnit-123', + adUnitCode, ...extra, }; } + +/** + * Creates a basic div element with specified ID and appends it to document body + * @param {string} id - The ID to assign to the div element + * @returns {HTMLDivElement} The created div element + */ +function createElement(id) { + const div = document.createElement('div'); + div.id = id; + div.style.width = '50px'; + div.style.height = '50px'; + div.style.background = 'black'; + + // Adjust frame dimensions if running within an iframe + if (frameElement) { + frameElement.style.width = '100px'; + frameElement.style.height = '100px'; + } + + window.document.body.appendChild(div); + + return div; +} + +/** + * Creates a visible element with mocked bounding client rect for testing + * @param {string} id - The ID to assign to the div element + * @returns {HTMLDivElement} The created div with mocked geometry + */ +function createElementVisible(id) { + const element = createElement(id); + // Mock client rect to simulate visible position in viewport + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 10, + y: 10, + }); + return element; +} + +/** + * Creates a completely hidden element (not rendered) using display: none + * @param {string} id - The ID to assign to the div element + * @returns {HTMLDivElement} The created hidden div element + */ +function createElementInvisible(id) { + const element = document.createElement('div'); + element.id = id; + element.style.display = 'none'; + + window.document.body.appendChild(element); + return element; +} + +/** + * Creates an invisible but space-reserved element using visibility: hidden + * with mocked bounding client rect for testing + * @param {string} id - The ID to assign to the div element + * @returns {HTMLDivElement} The created hidden div with mocked geometry + */ +function createElementHidden(id) { + const element = createElement(id); + element.style.visibility = 'hidden'; + // Mock client rect to simulate hidden element's geometry + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 100, + y: 100, + }); + return element; +} + +/** + * Removes an element from the DOM by its ID if it exists + * @param {string} id - The ID of the element to remove + */ +function removeElement(id) { + const element = document.getElementById(id); + if (element) { + element.remove(); + } +} From c0e4fee161a446c3d14c51348dedab8b13673112 Mon Sep 17 00:00:00 2001 From: quietPusher <129727954+quietPusher@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:12:58 +0200 Subject: [PATCH 006/147] new alias Vaaya media (#13995) Co-authored-by: mderevyanko --- modules/limelightDigitalBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 2cb24c3d360..40728c54245 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -47,7 +47,8 @@ export const spec = { { code: 'anzuExchange' }, { code: 'adnimation' }, { code: 'rtbdemand' }, - { code: 'altstar' } + { code: 'altstar' }, + { code: 'vaayaMedia' } ], supportedMediaTypes: [BANNER, VIDEO], From 07370a9a8280619a1f57bb1206528e1ecec46684 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 09:13:30 -0400 Subject: [PATCH 007/147] Bump karma-spec-reporter from 0.0.32 to 0.0.36 (#13911) Bumps [karma-spec-reporter](https://github.com/tmcgee123/karma-spec-reporter) from 0.0.32 to 0.0.36. - [Commits](https://github.com/tmcgee123/karma-spec-reporter/compare/v0.0.32...v0.0.36) --- updated-dependencies: - dependency-name: karma-spec-reporter dependency-version: 0.0.36 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Patrick McCann --- package-lock.json | 8 +++++--- package.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 511a844ddc2..13f81883af8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,7 +83,7 @@ "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.4.0", - "karma-spec-reporter": "^0.0.32", + "karma-spec-reporter": "^0.0.36", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", "merge-stream": "^2.0.0", @@ -15284,11 +15284,13 @@ } }, "node_modules/karma-spec-reporter": { - "version": "0.0.32", + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", + "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", "dev": true, "license": "MIT", "dependencies": { - "colors": "^1.1.2" + "colors": "1.4.0" }, "peerDependencies": { "karma": ">=0.9" diff --git a/package.json b/package.json index ee096b18146..433a9cad791 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.4.0", - "karma-spec-reporter": "^0.0.32", + "karma-spec-reporter": "^0.0.36", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", "merge-stream": "^2.0.0", From 001da515005dae5485d97fe25aec7b41d7e3e0fd Mon Sep 17 00:00:00 2001 From: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:58:18 +0200 Subject: [PATCH 008/147] ID5 User Id module - generate targeting tags on the server side (#13992) Including the tags in GAM is still controlled from the module configuration level. Generating them on the server side gives us more flexibility and simplifies user module. --- modules/id5IdSystem.js | 38 ++++----------- test/spec/modules/id5IdSystem_spec.js | 70 +++++---------------------- 2 files changed, 21 insertions(+), 87 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 5effa1bb463..d8f553c6ae0 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -570,36 +570,16 @@ function incrementNb(cachedObj) { function updateTargeting(fetchResponse, config) { if (config.params.gamTargetingPrefix) { - const tags = {}; - let universalUid = fetchResponse.universal_uid; - if (universalUid.startsWith('ID5*')) { - tags.id = "y"; - } - let abTestingResult = fetchResponse.ab_testing?.result; - switch (abTestingResult) { - case 'control': - tags.ab = 'c'; - break; - case 'normal': - tags.ab = 'n'; - break; - } - let enrichment = fetchResponse.enrichment; - if (enrichment?.enriched === true) { - tags.enrich = 'y'; - } else if (enrichment?.enrichment_selected === true) { - tags.enrich = 's'; - } else if (enrichment?.enrichment_selected === false) { - tags.enrich = 'c'; + const tags = fetchResponse.tags; + if (tags) { + 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 = 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]); - } - }); } } diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 1cd84e756d5..7249560c8c9 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1381,10 +1381,6 @@ describe('ID5 ID System', function () { id5System.id5IdSubmodule._reset() }); - function verifyTagging(tagName, tagValue) { - verifyMultipleTagging({[tagName]: tagValue}) - } - function verifyMultipleTagging(tagsObj) { expect(window.googletag.cmd.length).to.be.at.least(1); window.googletag.cmd.forEach(cmd => cmd()); @@ -1410,69 +1406,27 @@ describe('ID5 ID System', function () { expect(window.googletag.cmd).to.have.lengthOf(0) }) - it('should set GAM targeting for id tag when universal_uid starts with ID5*', function () { - // Setup + it('should not set GAM targeting if not returned from the server', function () { let config = utils.deepClone(getId5FetchConfig()); config.params.gamTargetingPrefix = "id5"; - let testObj = {...storedObject, universal_uid: 'ID5*test123'}; - id5System.id5IdSubmodule.decode(testObj, config); - - verifyTagging('id', 'y'); - }) - - it('should set GAM targeting for ab tag with control value', function () { - // Setup - let testObj = {...storedObject, ab_testing: {result: 'control'}}; - id5System.id5IdSubmodule.decode(testObj, targetingEnabledConfig); - - verifyTagging('ab', 'c'); - }) - - it('should set GAM targeting for ab tag with normal value', function () { - // Setup - let testObj = {...storedObject, ab_testing: {result: 'normal'}}; - id5System.id5IdSubmodule.decode(testObj, targetingEnabledConfig); - - verifyTagging('ab', 'n'); - }) - - it('should set GAM targeting for enrich tag with enriched=true', function () { - // Setup - let testObj = {...storedObject, enrichment: {enriched: true}}; - id5System.id5IdSubmodule.decode(testObj, targetingEnabledConfig); - - verifyTagging('enrich', 'y'); - }) - - it('should set GAM targeting for enrich tag with enrichment_selected=true', function () { - // Setup - let testObj = {...storedObject, enrichment: {enrichment_selected: true}}; - id5System.id5IdSubmodule.decode(testObj, targetingEnabledConfig); - - verifyTagging('enrich', 's'); - }) - - it('should set GAM targeting for enrich tag with enrichment_selected=false', function () { - // Setup - let testObj = {...storedObject, enrichment: {enrichment_selected: false}}; - id5System.id5IdSubmodule.decode(testObj, targetingEnabledConfig); - - verifyTagging('enrich', 'c'); + id5System.id5IdSubmodule.decode(storedObject, getId5FetchConfig()); + expect(window.googletag.cmd).to.have.lengthOf(0) }) - it('should set GAM targeting for multiple tags when all conditions are met', function () { + it('should set GAM targeting when tags returned if fetch response', function () { // Setup + let config = utils.deepClone(getId5FetchConfig()); + config.params.gamTargetingPrefix = "id5"; let testObj = { ...storedObject, - universal_uid: 'ID5*test123', - ab_testing: {result: 'normal'}, - enrichment: {enriched: true} + "tags": { + "id": "y", + "ab": "n", + "enrich": "y" + } }; + id5System.id5IdSubmodule.decode(testObj, config); - // Call decode once with the combined test object - id5System.id5IdSubmodule.decode(testObj, targetingEnabledConfig); - - // Verify all tags were set correctly verifyMultipleTagging({ 'id': 'y', 'ab': 'n', From fccbd804fa5ba5c40a997d303a19901de5393689 Mon Sep 17 00:00:00 2001 From: 152Media <54035983+152Media@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:03:35 -0300 Subject: [PATCH 009/147] Add 'oftmedia' to the approved external JS list (#14001) Co-authored-by: Andy --- src/adloader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/adloader.js b/src/adloader.js index 0a8e9b46669..098b78c211b 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -37,6 +37,7 @@ const _approvedLoadExternalJSList = [ 'nodalsAi', 'anonymised', 'optable', + 'oftmedia', // UserId Submodules 'justtag', 'tncId', From 8d75ccf28c62ee2f936775de6d82da8efd19cd57 Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Fri, 10 Oct 2025 15:18:48 +0100 Subject: [PATCH 010/147] Nodals RTD Module: Add support for publisher to override standard TCF consent checks (#14004) Co-authored-by: slimkrazy --- modules/nodalsAiRtdProvider.js | 15 +-- test/spec/modules/nodalsAiRtdProvider_spec.js | 103 ++++++++++++++++++ 2 files changed, 111 insertions(+), 7 deletions(-) diff --git a/modules/nodalsAiRtdProvider.js b/modules/nodalsAiRtdProvider.js index ac2900f6c7b..8cf0df0bde2 100644 --- a/modules/nodalsAiRtdProvider.js +++ b/modules/nodalsAiRtdProvider.js @@ -55,7 +55,7 @@ class NodalsAiRtdProvider { const params = config?.params || {}; if ( this.#isValidConfig(params) && - this.#hasRequiredUserConsent(userConsent) + this.#hasRequiredUserConsent(userConsent, config) ) { this.#propertyId = params.propertyId; this.#userConsent = userConsent; @@ -82,7 +82,7 @@ class NodalsAiRtdProvider { */ getTargetingData(adUnitArray, config, userConsent) { let targetingData = {}; - if (!this.#hasRequiredUserConsent(userConsent)) { + if (!this.#hasRequiredUserConsent(userConsent, config)) { return targetingData; } this.#userConsent = userConsent; @@ -104,7 +104,7 @@ class NodalsAiRtdProvider { } getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - if (!this.#hasRequiredUserConsent(userConsent)) { + if (!this.#hasRequiredUserConsent(userConsent, config)) { callback(); return; } @@ -133,7 +133,7 @@ class NodalsAiRtdProvider { } onBidResponseEvent(bidResponse, config, userConsent) { - if (!this.#hasRequiredUserConsent(userConsent)) { + if (!this.#hasRequiredUserConsent(userConsent, config)) { return; } this.#userConsent = userConsent; @@ -154,7 +154,7 @@ class NodalsAiRtdProvider { } onAuctionEndEvent(auctionDetails, config, userConsent) { - if (!this.#hasRequiredUserConsent(userConsent)) { + if (!this.#hasRequiredUserConsent(userConsent, config)) { return; } this.#userConsent = userConsent; @@ -240,11 +240,12 @@ class NodalsAiRtdProvider { /** * Checks if the user has provided the required consent. * @param {Object} userConsent - User consent object. + * @param {Object} config - Configuration object for the module. * @returns {boolean} - True if the user consent is valid, false otherwise. */ - #hasRequiredUserConsent(userConsent) { - if (!userConsent.gdpr || userConsent.gdpr?.gdprApplies === false) { + #hasRequiredUserConsent(userConsent, config) { + if (config?.params?.publisherProvidedConsent === true || !userConsent.gdpr || userConsent.gdpr?.gdprApplies === false) { return true; } if ( diff --git a/test/spec/modules/nodalsAiRtdProvider_spec.js b/test/spec/modules/nodalsAiRtdProvider_spec.js index f07d13fd11b..486822f3934 100644 --- a/test/spec/modules/nodalsAiRtdProvider_spec.js +++ b/test/spec/modules/nodalsAiRtdProvider_spec.js @@ -147,6 +147,7 @@ describe('NodalsAI RTD Provider', () => { const noPurpose1UserConsent = generateGdprConsent({ purpose1Consent: false }); const noPurpose7UserConsent = generateGdprConsent({ purpose7Consent: false }); const outsideGdprUserConsent = generateGdprConsent({ gdprApplies: false }); + const leastPermissiveUserConsent = generateGdprConsent({ purpose1Consent: false, purpose7Consent: false, nodalsConsent: false }); beforeEach(() => { sandbox = sinon.createSandbox(); @@ -263,6 +264,15 @@ describe('NodalsAI RTD Provider', () => { expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); + + it('should return true with publisherProvidedConsent flag set and least permissive consent', function () { + const configWithManagedConsent = { params: { propertyId: '10312dd2', publisherProvidedConsent: true } }; + const result = nodalsAiRtdSubmodule.init(configWithManagedConsent, leastPermissiveUserConsent); + server.respond(); + + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); }); describe('when initialised with valid config and data already in storage', () => { @@ -617,6 +627,24 @@ describe('NodalsAI RTD Provider', () => { expect(result).to.deep.equal({}); }); + + it('should return targeting data with publisherProvidedConsent flag set and least permissive consent', () => { + createTargetingEngineStub(engineGetTargetingDataReturnValue); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const configWithManagedConsent = { params: { propertyId: '10312dd2', publisherProvidedConsent: true } }; + + const result = nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + configWithManagedConsent, + leastPermissiveUserConsent + ); + server.respond(); + + expect(result).to.deep.equal(engineGetTargetingDataReturnValue); + }); }); describe('getBidRequestData()', () => { @@ -703,6 +731,33 @@ describe('NodalsAI RTD Provider', () => { expect(args[3].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); expect(server.requests.length).to.equal(0); }); + + it('should proxy the correct data to engine.getBidRequestData with publisherProvidedConsent flag set and least permissive consent', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const callback = sinon.spy(); + const reqBidsConfigObj = {dummy: 'obj'} + const configWithManagedConsent = { params: { propertyId: '10312dd2', publisherProvidedConsent: true } }; + nodalsAiRtdSubmodule.getBidRequestData( + reqBidsConfigObj, callback, configWithManagedConsent, leastPermissiveUserConsent + ); + server.respond(); + + expect(callback.called).to.be.false; + expect(engine.init.called).to.be.true; + expect(engine.getBidRequestData.called).to.be.true; + const args = engine.getBidRequestData.getCall(0).args; + expect(args[0]).to.deep.equal(reqBidsConfigObj); + expect(args[1]).to.deep.equal(callback); + expect(args[2]).to.deep.equal(leastPermissiveUserConsent); + expect(args[3].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[3].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[3].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); }); describe('onBidResponseEvent()', () => { @@ -782,6 +837,30 @@ describe('NodalsAI RTD Provider', () => { expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); expect(server.requests.length).to.equal(0); }); + + it('should proxy the correct data to engine.onBidResponseEvent with publisherProvidedConsent flag set and least permissive consent', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const bidResponse = {dummy: 'obj', 'bid': 'foo'}; + const configWithManagedConsent = { params: { propertyId: '10312dd2', publisherProvidedConsent: true } }; + nodalsAiRtdSubmodule.onBidResponseEvent( + bidResponse, configWithManagedConsent, leastPermissiveUserConsent + ); + server.respond(); + + expect(engine.init.called).to.be.true; + expect(engine.onBidResponseEvent.called).to.be.true; + const args = engine.onBidResponseEvent.getCall(0).args; + expect(args[0]).to.deep.equal(bidResponse); + expect(args[1]).to.deep.equal(leastPermissiveUserConsent); + expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); }); describe('onAuctionEndEvent()', () => { @@ -861,5 +940,29 @@ describe('NodalsAI RTD Provider', () => { expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); expect(server.requests.length).to.equal(0); }); + + it('should proxy the correct data to engine.onAuctionEndEvent with publisherProvidedConsent flag set and least permissive consent', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const auctionDetails = {dummy: 'obj', auction: 'foo'}; + const configWithManagedConsent = { params: { propertyId: '10312dd2', publisherProvidedConsent: true } }; + nodalsAiRtdSubmodule.onAuctionEndEvent( + auctionDetails, configWithManagedConsent, leastPermissiveUserConsent + ); + server.respond(); + + expect(engine.init.called).to.be.true; + expect(engine.onAuctionEndEvent.called).to.be.true; + const args = engine.onAuctionEndEvent.getCall(0).args; + expect(args[0]).to.deep.equal(auctionDetails); + expect(args[1]).to.deep.equal(leastPermissiveUserConsent); + expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); }); }); From 05e5e99e7e1a2b947bb450f7943ca7e40d0ca52a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 13 Oct 2025 07:23:34 -0700 Subject: [PATCH 011/147] Build system: start browserstack binary explicitly (#13999) * Core: wait for creative document DOMContentLoaded * start browserstacklocal explicitly * debug * debug * capitalization * start as daemon * Undo unrelated change --- .github/workflows/test-chunk.yml | 4 ++++ .github/workflows/test.yml | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/test-chunk.yml b/.github/workflows/test-chunk.yml index faba70b16a7..ef5e26729e0 100644 --- a/.github/workflows/test-chunk.yml +++ b/.github/workflows/test-chunk.yml @@ -55,6 +55,10 @@ jobs: key: ${{ inputs.wdir }} fail-on-cache-miss: true + - name: Start BrowserstackLocal + run: | + ./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY --daemon start + - name: Run tests uses: nick-fields/retry@v3 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 690bee6a5eb..2fc4e434240 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,11 @@ jobs: - name: Install dependencies run: npm ci + - name: Download Browserstack binary + run: | + wget https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip + unzip BrowserStackLocal-linux-x64.zip + - name: Cache source uses: actions/cache/save@v4 with: @@ -132,6 +137,11 @@ jobs: path: . key: source-${{ github.run_id }} fail-on-cache-miss: true + + - name: Start BrowserstackLocal + run: | + ./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY --daemon start + - name: Run tests uses: nick-fields/retry@v3 with: From fb9dad1d102c4a61dff52f5cbb04c829467b862f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:18:01 -0400 Subject: [PATCH 012/147] Bump github/codeql-action from 3 to 4 (#14006) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' 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> Co-authored-by: Patrick McCann --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index aaeb89e9815..f86cd38a43c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -57,7 +57,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -70,4 +70,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From ee900eb648da036cc53c62a00ac23d35eba25aff Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:32:36 -0400 Subject: [PATCH 013/147] Msft Bid adapter - initial release (for closed testing) (#13952) * msftBidAdapter - new adapter to eventually replace appnexusBidAdapter * add unit test file * fix import issue in tests * fix lint issue in adapter file * add more tests * add more tests * update banner test * update comment * another set of updates * update unit tests * update to readme * remove tid code * remove some comments, fix type checks, remove unneeded response function * edit other comments * try to fix lint errors --------- Co-authored-by: Demetrio Girardi --- modules/msftBidAdapter.js | 579 +++++++++ modules/msftBidAdapter.md | 272 +++++ test/spec/modules/msftBidAdapter_spec.js | 1413 ++++++++++++++++++++++ 3 files changed, 2264 insertions(+) create mode 100644 modules/msftBidAdapter.js create mode 100644 modules/msftBidAdapter.md create mode 100644 test/spec/modules/msftBidAdapter_spec.js diff --git a/modules/msftBidAdapter.js b/modules/msftBidAdapter.js new file mode 100644 index 00000000000..a386b83733c --- /dev/null +++ b/modules/msftBidAdapter.js @@ -0,0 +1,579 @@ +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 { + deepAccess, + deepSetValue, + getParameterByName, + isArray, + isArrayOfNums, + isNumber, + isStr, + logError, + logMessage, + logWarn, + mergeDeep +} from "../src/utils.js"; + +const BIDDER_CODE = "msft"; +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 ENDPOINT_URL_NORMAL = "https://ib.adnxs.com/openrtb2/prebidjs"; +const ENDPOINT_URL_SIMPLE = "https://ib.adnxs-simple.com/openrtb2/prebidjs"; +const GVLID = 32; +const RESPONSE_MEDIA_TYPE_MAP = { + 0: BANNER, + 1: VIDEO, + 3: NATIVE +}; +const SOURCE = "pbjs"; + +const storage = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +/** + * STUFF FOR REQUEST SIDE + * + * list of old appnexus bid params -> how to set them now for msft adapter -> where are they in the openRTB request + * params.placement_id -> params.placement_id -> ext.appnexus.placement_id DONE + * params.member -> params.member -> query string as `member_id` DONE + * params.inv_code -> params.inv_code -> imp.tagid DONE + * params.publisher_id -> ortb.publisher.id -> publisher.id DONE + * params.frameworks -> params.banner_frameworks -> banner.api (array of ints) DONE + * params.user -> ortb.user -> user DONE + * params.allow_smaller_sizes -> params.allow_smaller_sizes -> imp.ext.appnexus.allow_smaller_sizes DONE + * params.use_pmt_rule -> params.use_pmt_rule -> ext.appnexus.use_pmt_rule (boolean) DONE + * params.keywords -> params.keywords (for tag level keywords) -> imp.ext.appnexus.keywords (comma delimited string) DONE + * params.video -> mediaTypes.video -> imp.video DONE + * params.video.frameworks -> mediatypes.video.api -> imp.video.api (array of ints) DONE + * params.app -> ortb.app -> app DONE + * params.reserve -> bidfloor module -> imp.bidfloor DONE + * params.position -> mediaTypes.banner.pos -> imp.banner.pos DONE + * params.traffic_source_code -> params.traffic_source_code -> imp.ext.appnexus.traffic_source_code DONE + * params.supply_type -> ortb.site/app -> site/app DONE + * params.pub_click -> params.pubclick -> imp.ext.appnexus.pubclick DONE + * params.ext_inv_code -> params.ext_inv_code -> imp.ext.appnexus.ext_inv_code DONE + * params.external_imp_id -> params.ext_imp_id -> imp.id (overrides default imp.id) DONE + * + * list of ut.tags[] fields that weren't tied to bid params -> where they were read before -> where they go in the openRTB request + * uuid -> set in adapter -> imp.id DONE + * primary_size -> imp.banner.w and imp.banner.h (if not already set from mediaTypes.banner.sizes) DONE + * sizes -> mediaTypes.banner.sizes -> imp.banner.format DONE + * ad_types -> mediaTypes.banner/video/native -> imp.banner/video/native DONE + * gpid -> ortb2Imp.ext.gpid (from ortb) -> imp.ext.gpid (from ortb) DONE + * tid -> ortb.source.tid (from ortb) -> source.tid DONE? + * hb_source -> set in adapter -> ext.appnexus.hb_source DONE + * native -> mediaTypes.native (ORTB version) -> imp.native DONE + * + * list of ut fields that weren't tied to bid params -> where they were read before -> where they go in the openRTB request + * schain -> set in adapter from bidRequest.schain -> source.ext.schain DONE + * iab_support -> set in adapter from mediaTypes.video.api and bid params.frameworks -> source.ext.omidpn and source.ext.omidpv DONE + * device -> was part of bid.params.app (read now from ortb.device) -> device DONE + * keywords -> getConfig('appnexusAuctionKeywords') (read now from ortb.site/user) -> site/user DONE + * gdpr_consent -> set in adapter from bidderRequest.gdprConsent -> regs.ext.gdpr and user.ext.consent DONE + * privacy -> set in adapter from bidderRequest.uspConsent -> regs.ext.privacy DONE + * eids -> set in adapter from bidRequest.userId -> user.ext.eids DONE + * dsa -> set in adapter from ortb.regs.ext.dsa -> regs.ext.dsa DONE + * coppa -> getConfig('coppa') -> regs.coppa DONE + * require_asset_url -> mediaTypes.video.context === 'instream' -> imp.ext.appnexus.require_asset_url DONE + */ + +/** + * STUFF FOR RESPONSE SIDE + * + * new bid response fields ib is adding + * old field from UT -> new field in ortb bid response -> where it goes in the bidResponse object + * advertiser_id -> imp.ext.appnexus.advertiser_id -> bidResponse.advertiserId DONE + * renderer_config -> imp.ext.appnexus.renderer_config -> bidResponse.rendererConfig DONE + * renderer_id -> imp.ext.appnexus.renderer_id -> bidResponse.rendererId DONE + * asset_url -> imp.ext.appnexus.asset_url -> bidResponse.assetUrl DONE + * + */ + +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: true, + ttl: 300, + }, + imp(buildImp, bidRequest, context) { + const extANData = {}; + const bidderParams = bidRequest.params; + const imp = buildImp(bidRequest, context); + // banner.topframe, banner.format, banner.pos are handled in processors/banner.js + // video.mimes, video.protocols, video.w, video.h, video.startdelay are handled in processors/video.js + // native request is handled in processors/native.js + if (imp.banner && !imp.banner.w && !imp.banner.h) { + const primarySizeObj = deepAccess(imp, 'banner.format.0'); + if (primarySizeObj && isNumber(primarySizeObj.w) && isNumber(primarySizeObj.h)) { + imp.banner.w = primarySizeObj.w; + imp.banner.h = primarySizeObj.h; + } + } + + if (imp?.banner && !imp.banner.api) { + const bannerFrameworks = bidderParams.banner_frameworks; + if (isArrayOfNums(bannerFrameworks)) { + imp.banner.api = bannerFrameworks; + } + } + + if (FEATURES.VIDEO && imp?.video) { + if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'instream') { + extANData.require_asset_url = true; + } + + if (imp.video.plcmt) { + imp.video.placement = imp.video.plcmt; + delete imp.video.plcmt; + } + } + + if (bidderParams) { + if (bidderParams.placement_id) { + extANData.placement_id = bidderParams.placement_id; + } else if (bidderParams.inv_code) { + deepSetValue(imp, 'tagid', bidderParams.inv_code); + } + + const optionalParamsTypeMap = { + allow_smaller_sizes: 'boolean', + use_pmt_rule: 'boolean', + keywords: 'string', + traffic_source_code: 'string', + pubclick: 'string', + ext_inv_code: 'string', + ext_imp_id: 'string' + }; + Object.entries(optionalParamsTypeMap).forEach(([paramName, paramType]) => { + if (checkOptionalParams(bidRequest, paramName, paramType)) { + if (paramName === 'ext_imp_id') { + imp.id = bidderParams.ext_imp_id; + return; + } + extANData[paramName] = bidderParams[paramName]; + } + }); + } + + // for force creative we expect the following format: + // page.html?ast_override_div=divId:creativeId,divId2:creativeId2 + const overrides = getParameterByName('ast_override_div'); + if (isNotEmptyString(overrides)) { + const adUnitOverride = decodeURIComponent(overrides).split(',').find((pair) => pair.startsWith(`${bidRequest.adUnitCode}:`)); + if (adUnitOverride) { + const forceCreativeId = adUnitOverride.split(':')[1]; + if (forceCreativeId) { + extANData.force_creative_id = parseInt(forceCreativeId, 10); + } + } + } + + if (Object.keys(extANData).length > 0) { + deepSetValue(imp, 'ext.appnexus', extANData); + } + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + if (request?.user?.ext?.eids.length > 0) { + request.user.ext.eids.forEach(eid => { + if (eid.source === 'adserver.org') { + eid.rti_partner = 'TDID'; + } else if (eid.source === 'uidapi.com') { + eid.rti_partner = 'UID2'; + } + }); + } + + const extANData = { + prebid: true, + hb_source: 1, + sdk: { + version: '$prebid.version$', + source: SOURCE + } + }; + + if (bidderRequest?.refererInfo) { + const refererinfo = { + 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(',') + }; + const pubPageUrl = bidderRequest.refererInfo.canonicalUrl; + if (isNotEmptyString(pubPageUrl)) { + refererinfo.rd_can = pubPageUrl; + } + extANData.referrer_detection = refererinfo; + } + + deepSetValue(request, 'ext.appnexus', extANData); + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + + // first derive the mediaType from bid data + let mediaType; + const bidAdType = bid?.ext?.appnexus?.bid_ad_type; + const extANData = deepAccess(bid, 'ext.appnexus'); + + if (isNumber(bidAdType) && RESPONSE_MEDIA_TYPE_MAP.hasOwnProperty(bidAdType)) { + context.mediaType = mediaType = RESPONSE_MEDIA_TYPE_MAP[bidAdType]; + } + const bidResponse = buildBidResponse(bid, context); + + if (extANData.advertiser_id) { + bidResponse.meta = Object.assign({}, bidResponse.meta, { + advertiser_id: extANData.advertiser_id + }); + } + + // replace the placeholder token for trk.js if it's present in eventtrackers + if (FEATURES.NATIVE && mediaType === NATIVE) { + try { + const nativeAdm = bid.adm ? JSON.parse(bid.adm) : {}; + if (nativeAdm?.eventtrackers && isArray(nativeAdm.eventtrackers)) { + nativeAdm.eventtrackers.forEach(trackCfg => { + if (trackCfg.url.includes('dom_id=%native_dom_id%')) { + const prebidParams = 'pbjs_adid=' + bidResponse.adId + ';pbjs_auc=' + bidRequest.adUnitCode; + trackCfg.url = trackCfg.url.replace('dom_id=%native_dom_id%', prebidParams); + } + }); + } + } catch (e) { + logError('MSFT Native adm parse error', e); + } + } + + if (FEATURES.VIDEO && mediaType === VIDEO) { + // handle outstream bids, ie setup the renderer + if (extANData?.renderer_url && extANData?.renderer_id) { + const adUnitCode = bidRequest?.adUnitCode; + if (isNotEmptyString(adUnitCode)) { + // rendererOptions here should be treated as any publisher options for outstream ... + // ...set within the adUnit.mediaTypes.video.renderer.options or in the adUnit.renderer.options + let rendererOptions = deepAccess(bidRequest, 'mediaTypes.video.renderer.options'); + if (!rendererOptions) { + rendererOptions = deepAccess(bidRequest, 'renderer.options'); + } + + // populate imbpus config options in the bidReponse.adResponse.ad object for our outstream renderer to use later + // renderer_config should be treated as the old rtb.rendererOptions that came from the bidresponse.adResponse + if (!bidResponse.adResponse) { + 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 + }; + } + + bidResponse.renderer = newRenderer(adUnitCode, { + renderer_url: extANData.renderer_url, + renderer_id: extANData.renderer_id, + }, rendererOptions); + } + } else { + // handle instream bids + // if nurl and asset_url was set, we need to populate vastUrl field + if (bid.nurl && extANData?.asset_url) { + bidResponse.vastUrl = bid.nurl + '&redir=' + encodeURIComponent(extANData.asset_url); + } + // if not populated, the VAST in the adm will go to the vastXml field by the ortb converter + } + } + + return bidResponse; + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: [], // TODO fill in after full transition or as seperately requested + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + isBidRequestValid: (bid) => { + const params = bid.params; + return !!( + (typeof params.placement_id === 'number') || + (typeof params.member === 'number' && isNotEmptyString(params?.inv_code)) + ); + }, + + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ + bidRequests, + bidderRequest, + }); + + const omidSupport = ((bidRequests) || []).find(hasOmidSupport); + if (omidSupport) { + mergeDeep( + data, { + source: { + ext: { + omidpn: 'AppNexus', + omidpv: '$prebid.version$' + } + } + }, + data); + } + + // TODO remove later + logMessage("MSFT openRTB request", data); + + return formatRequest(data, bidderRequest); + }, + + interpretResponse(response, request) { + const bids = converter.fromORTB({ + response: response.body, + request: request.data, + }).bids; + + return bids; + }, + + getUserSyncs: function ( + syncOptions, + responses, + gdprConsent, + uspConsent, + gppConsent + ) { + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + return [{ + type: "iframe", + url: "https://acdn.adnxs.com/dmp/async_usersync.html", + }, ]; + } + + if (syncOptions.pixelEnabled) { + // first attempt using static list + const imgList = ["https://px.ads.linkedin.com/setuid?partner=appNexus"]; + return imgList.map((url) => ({ + type: "image", + url, + })); + } + }, +}; + +function isNotEmptyString(value) { + return isStr(value) && value !== ''; +} + +function checkOptionalParams(bidRequest, fieldName, expectedType) { + const value = bidRequest?.params?.[fieldName]; + // allow false, but not undefined, null or empty string + if (value !== undefined || value !== null || value !== '') { + const actualType = typeof value; + if (actualType === expectedType) { + return true; + } else { + logWarn(`Removing invalid bid.param ${fieldName} for adUnitCode ${bidRequest.adUnitCode}, expected ${expectedType}`); + return false; + } + } + return false; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + const options = { + withCredentials: true, + }; + + let endpointUrl = ENDPOINT_URL_NORMAL; + if (!hasPurpose1Consent(bidderRequest.gdprConsent)) { + endpointUrl = ENDPOINT_URL_SIMPLE; + } + + // handle debug info here if needed + let debugObj = {}; + const debugCookieName = 'apn_prebid_debug'; + const debugCookie = storage.getCookie(debugCookieName) || null; + + // first check cookie + if (debugCookie) { + try { + debugObj = JSON.parse(debugCookie); + } catch (e) { + logError('MSFT Debug Auction Cookie Error:\n\n' + e); + } + } else { + // then check query params + Object.keys(DEBUG_QUERY_PARAM_MAP).forEach(qparam => { + const qval = getParameterByName(qparam); + if (isStr(qval) && qval !== '') { + debugObj[DEBUG_QUERY_PARAM_MAP[qparam]] = qval; + // keep 'enabled' for old setups still using the cookie, switch to 'debug' when passing to query params + } + }); + if (Object.keys(debugObj).length > 0 && !debugObj.hasOwnProperty('enabled')) debugObj.enabled = true; + } + + if (debugObj?.enabled) { + endpointUrl += '?' + Object.keys(debugObj) + .filter(param => DEBUG_PARAMS.includes(param)) + .map(param => (param === 'enabled') ? `debug=${debugObj[param]}` : `${param}=${debugObj[param]}`) + .join('&'); + } + + // check if member is defined in the bid params + const memberId = ((bidderRequest?.bids) || []).find(bid => bid.params && bid.params.member && isNumber(bid.params.member)); + if (memberId) { + endpointUrl += (endpointUrl.indexOf('?') === -1 ? '?' : '&') + 'member_id=' + memberId; + } + + // temporary setting + endpointUrl += (endpointUrl.indexOf('?') === -1 ? '?' : '&') + 'eqt=1'; + + if (getParameterByName("apn_test").toUpperCase() === "TRUE") { + options.customHeaders = { + "X-Is-Test": 1, + }; + } + + request.push({ + method: "POST", + url: endpointUrl, + data: payload, + bidderRequest, + options, + }); + + return request; +} + +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); + } + + renderer.setEventHandlers({ + impression: () => logMessage("AppNexus outstream video impression event"), + loaded: () => logMessage("AppNexus outstream video loaded event"), + ended: () => { + logMessage("AppNexus outstream renderer video event"); + document.querySelector(`#${adUnitCode}`).style.display = "none"; + }, + }); + return renderer; +} + +/** + * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. + * @param {string} elementId element id + */ +function hidedfpContainer(elementId) { + try { + const el = document + .getElementById(elementId) + .querySelectorAll("div[id^='google_ads']"); + if (el[0]) { + el[0].style.setProperty("display", "none"); + } + } catch (e) { + // element not found! + } +} + +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?.localName === "iframe") { + el[0].nextSibling.style.setProperty("display", "none"); + } + } catch (e) { + // element not found! + } +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ + id, + eventName, + }); +} + +function outstreamRender(bid, doc) { + hidedfpContainer(bid.adUnitCode); + hideSASIframe(bid.adUnitCode); + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + const win = doc?.defaultView || window; + win.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split("x")], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.requestId, + adResponse: bid.adResponse, // fix + rendererOptions: bid.renderer.getConfig(), + }, + handleOutstreamRendererEvents.bind(null, bid) + ); + }); +} + +function hasOmidSupport(bid) { + // read from mediaTypes.video.api = 7 + // read from bid.params.frameworks = 6 (for banner) + // change >> ignore bid.params.video.frameworks = 6 (prefer mediaTypes.video.api) + let hasOmid = false; + const bidderParams = bid?.params; + const videoParams = bid?.mediaTypes?.video?.api; + if (bidderParams?.frameworks && isArray(bidderParams.frameworks)) { + hasOmid = bid.params.frameworks.includes(6); + } + if (!hasOmid && isArray(videoParams) && videoParams.length > 0) { + hasOmid = videoParams.includes(7); + } + return hasOmid; +} + +registerBidder(spec); diff --git a/modules/msftBidAdapter.md b/modules/msftBidAdapter.md new file mode 100644 index 00000000000..df5be0a1fb6 --- /dev/null +++ b/modules/msftBidAdapter.md @@ -0,0 +1,272 @@ +# Overview + +``` +Module Name: Microsoft Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@microsoft.com +``` + +# Description + +The Microsoft Bid Adapter connects to Microsoft's advertising exchange for bids. This adapter supports banner, video (instream and outstream), and native ad formats using OpenRTB 2.5 standards. + +The Microsoft adapter requires setup and approval from the Microsoft Advertising team. Please reach out to your account team for more information. + +# Migration from AppNexus Bid Adapter + +## Bid Parameters + +If you are migrating from the AppNexus bid adapter, the following table shows how the bid parameters have changed: + +| AppNexus Parameter | Microsoft Parameter | Description | +|-------------------|-------------------|-------------| +| `params.placementId` | `params.placement_id` | Placement ID (**only** the underscore case is now supported) | +| `params.member` | `params.member` | Member ID (unchanged) | +| `params.inv_code` | `params.inv_code` | Inventory code (unchanged) | +| `params.publisher_id` | Use `ortb2.publisher.id` | Publisher ID (moved to ortb2 config) | +| `params.frameworks` | `params.banner_frameworks` | Banner API frameworks array | +| `params.user` | Use `ortb2.user` | User data (moved to ortb2 config) | +| `params.allow_smaller_sizes` | `params.allow_smaller_sizes` | Allow smaller ad sizes (unchanged) | +| `params.use_pmt_rule` | `params.use_pmt_rule` | Use payment rule (unchanged) | +| `params.keywords` | `params.keywords` | Tag/Imp-level keywords (use ORTB format of comma-delimited string value; eg pet=cat,food,brand=fancyfeast) | +| `params.video` | Use `mediaTypes.video` | Video parameters (moved to mediaTypes) | +| `params.video.frameworks` | Use `mediaTypes.video.api` | Video API frameworks (moved to mediaTypes) | +| `params.app` | Use `ortb2.app` | App data (moved to ortb2 config) | +| `params.reserve` | Use bidfloor module | Reserve price (use bidfloor module) | +| `params.position` | Use `mediaTypes.banner.pos` | Banner position (moved to mediaTypes) | +| `params.traffic_source_code` | `params.traffic_source_code` | Traffic source code (unchanged) | +| `params.supply_type` | Use `ortb2.site` or `ortb2.app` | Supply type (moved to ortb2 config) | +| `params.pub_click` | `params.pubclick` | Publisher click URL (dropped underscore to align to endpoint) | +| `params.ext_inv_code` | `params.ext_inv_code` | External inventory code (unchanged) | +| `params.external_imp_id` | `params.ext_imp_id` | External impression ID (shortend to ext) | + +## Migration Example + +**Before (AppNexus):** +```javascript +{ + bidder: "appnexus", + params: { + placementId: "12345", + member: "123", + publisher_id: "456", + frameworks: [1, 2], + position: "above", + reserve: 0.50, + keywords: "category=sports,team=football" + } +} +``` + +**After (Microsoft):** +```javascript +{ + bidder: "msft", + params: { + placement_id: "12345", + member: "123", + banner_frameworks: [1, 2], + keywords: "category=sports,team=football" + } +} +``` + +## Native + +If you are migrating from the AppNexus bid adapter, the setup for Native adUnits now require the use of the Prebid.js ORTB Native setup. The Microsoft Bid Adapter no longer offers support to the legacy Prebid.js Native adUnit setup. Requests using that approach will not work and need to be converted to the equivalent values in the adUnit. This change is made to better align with Prebid.js and many other Bid Adapters that support Native in an ORTB context. + +Please refer to the [Prebid.js Native Implementation Guide](https://docs.prebid.org/prebid/native-implementation.html) if you need additional information to implement the setup. + +# Test Parameters + +## Banner +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + bids: [ + { + bidder: "msft", + params: { + placement_id: "12345" + } + } + ] + } +]; +``` + +## Video +```javascript +var videoAdUnit = { + code: 'video-ad-unit', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [2, 3], + maxduration: 30, + api: [2] + } + }, + bids: [ + { + bidder: 'msft', + params: { + placement_id: "67890" + } + } + ] +}; +``` + +## Native +```javascript +var nativeAdUnit = { + code: 'native-ad-unit', + mediaTypes: { + native: { + ortb: { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 300 + } + }, { + id: 2, + required: 1, + title: { + len: 100, + } + }, { + id: 3, + required: 1, + data: { + type: 1 + } + }] + } + } + }, + bids: [ + { + bidder: 'msft', + params: { + placement_id: "13579" + } + } + ] +}; +``` + +## Multi-format Ad Unit +```javascript +var multiFormatAdUnit = { + code: 'multi-format-ad-unit', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [ + { + bidder: 'msft', + params: { + member: "123", + inv_code: "test_inv_code", + allow_smaller_sizes: true, + banner_frameworks: [1, 2], + keywords: "category=news,section=sports" + } + } + ] +}; +``` + +# Supported Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `placement_id` | String | Yes* | Placement ID from Microsoft Advertising | +| `member` | String | Yes* | Member ID (required if placement_id not provided) | +| `inv_code` | String | Yes* | Inventory code (required if placement_id not provided) | +| `allow_smaller_sizes` | Boolean | No | Allow smaller ad sizes than requested | +| `use_pmt_rule` | Boolean | No | Use payment rule | +| `keywords` | String | No | Comma-delimited keywords for targeting | +| `traffic_source_code` | String | No | Traffic source identifier | +| `pubclick` | String | No | Publisher click URL | +| `ext_inv_code` | String | No | External inventory code | +| `ext_imp_id` | String | No | External impression ID | +| `banner_frameworks` | Array of Integers | No | Supported banner API frameworks | + +*Either `placement_id` OR both `member` and `inv_code` are required. + +# Configuration + +## Global Configuration +```javascript +pbjs.setConfig({ + ortb2: { + site: { + publisher: { + id: "your-publisher-id" + } + }, + user: { + keywords: "global,keywords,here" + } + } +}); +``` + +## Floor Prices +```javascript +pbjs.setConfig({ + floors: { + enforcement: { + enforceJS: true, + floorDeals: true + }, + data: { + currency: 'USD', + schema: { + delimiter: '*', + fields: ['mediaType', 'size'] + }, + values: { + 'banner*300x250': 0.50, + 'video*640x480': 1.00, + '*': 0.25 + } + } + } +}); +``` + +# User Sync + +The Microsoft adapter supports both iframe and pixel user syncing. It will attempt iframe sync first if enabled and GDPR consent is available, otherwise it will fall back to pixel sync. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + pixelEnabled: true, + syncDelay: 3000 + } +}); +``` diff --git a/test/spec/modules/msftBidAdapter_spec.js b/test/spec/modules/msftBidAdapter_spec.js new file mode 100644 index 00000000000..a7965e7d845 --- /dev/null +++ b/test/spec/modules/msftBidAdapter_spec.js @@ -0,0 +1,1413 @@ +import { + expect +} from 'chai'; +import { + spec +} from 'modules/msftBidAdapter.js'; +import { + BANNER, + VIDEO, + NATIVE +} from '../../../src/mediaTypes.js'; +import { + deepClone +} from '../../../src/utils.js'; + +const ENDPOINT_URL_NORMAL = 'https://ib.adnxs.com/openrtb2/prebidjs'; + +describe('msftBidAdapter', function () { + const baseBidRequests = { + bidder: 'msft', + adUnitCode: 'adunit-code', + bidId: '2c5f3044f546f1', + params: { + placement_id: 12345 + } + }; + + const baseBidderRequest = { + auctionId: 'test-auction-id', + ortb2: { + site: { + page: 'http://www.example.com/page.html', + domain: 'example.com' + }, + user: { + ext: { + eids: [{ + source: 'adserver.org', + uids: [{ + id: '12345', + atype: 1 + }] + }, { + source: 'uidapi.com', + uids: [{ + id: '12345', + atype: 1 + }] + }] + } + } + }, + refererInfo: { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": ['http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true'], + "topmostLocation": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "location": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "canonicalUrl": 'http://www.example.com/page.html', + "page": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "domain": "test.localhost:9999", + "ref": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "referer": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "canonicalUrl": null + } + }, + bids: baseBidRequests, + gdprConsent: { + gdprApplies: true, + consentString: 'test-consent-string', + vendorData: { + purpose: { + consents: { + 1: true + } + } + } + } + }; + + describe('isBidRequestValid', function () { + it('should return true when required params are present (placement_id)', function () { + const bid = { + bidder: 'msft', + params: { + placement_id: 12345 + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params are present (member and inv_code)', function () { + const bid = { + bidder: 'msft', + params: { + member: 123, + inv_code: 'abc' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not present', function () { + const bid = { + bidder: 'msft', + params: { + member: 123 + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not the correct type', function () { + const bid = { + bidder: 'msft', + params: { + placement_id: '12345' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false, 'placement_id is string, should be number'); + + bid.params = { + member: '123', + inv_code: 'abc' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false, 'member is string, should be number'); + + bid.params = { + member: 123, + inv_code: 123 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false, 'inv_code is number, should be string'); + }); + + it('should build a basic banner request', function () { + let testBidRequest = deepClone(baseBidRequests); + testBidRequest.params = Object.assign({}, testBidRequest.params, { + banner_frameworks: [1, 2, 6], + allow_smaller_sizes: false, + use_pmt_rule: true, + keywords: 'sports,music=rock', + traffic_source_code: 'some_traffic_source', + pubclick: 'http://publisher.click.url', + ext_inv_code: 'inv_code_123', + ext_imp_id: 'ext_imp_id_456' + }); + const bidRequests = [{ + ...testBidRequest, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + }]; + + const testBidderRequest = deepClone(baseBidderRequest); + const bidderRequest = Object.assign({}, testBidderRequest, { + bids: bidRequests + }); + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.satisfy(url => url.startsWith(ENDPOINT_URL_NORMAL)); + const data = request.data; + expect(data).to.exist; + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].banner.format).to.deep.equal([{ + w: 300, + h: 250 + }, { + w: 300, + h: 600 + }]); + expect(data.imp[0].banner.api).to.deep.equal([1, 2, 6]); + expect(data.imp[0].ext.appnexus.placement_id).to.equal(12345); + expect(data.imp[0].ext.appnexus.allow_smaller_sizes).to.equal(false); + expect(data.imp[0].ext.appnexus.use_pmt_rule).to.equal(true); + expect(data.imp[0].ext.appnexus.keywords).to.equal('sports,music=rock'); + expect(data.imp[0].ext.appnexus.traffic_source_code).to.equal('some_traffic_source'); + expect(data.imp[0].ext.appnexus.pubclick).to.equal('http://publisher.click.url'); + expect(data.imp[0].ext.appnexus.ext_inv_code).to.equal('inv_code_123'); + expect(data.imp[0].id).to.equal('ext_imp_id_456'); + }); + + if (FEATURES.VIDEO) { + it('should build a video request', function () { + const testBidRequests = deepClone(baseBidRequests); + const testBidderRequest = deepClone(baseBidderRequest); + + const bidRequests = [{ + ...testBidRequests, + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [640, 480] + ], + plcmt: 4, + mimes: ['video/mp4'], + protocols: [2, 3], + api: [2] + } + } + }]; + const bidderRequest = Object.assign({}, testBidderRequest, { + bids: bidRequests + }); + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.satisfy(url => url.startsWith(ENDPOINT_URL_NORMAL)); + const data = request.data; + expect(data).to.exist; + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].video.placement).to.equal(4); + expect(data.imp[0].video.w).to.equal(640); + expect(data.imp[0].video.h).to.equal(480); + expect(data.imp[0].ext.appnexus.require_asset_url).to.be.true; + }); + } + + if (FEATURES.NATIVE) { + it('should build a native request', function () { + const testBidRequest = deepClone(baseBidRequests); + const testBidderRequest = deepClone(baseBidderRequest); + + testBidRequest.params = { + member: 123, + inv_code: 'inv_code_123' + } + const nativeRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 140 + } + }], + context: 1, + plcmttype: 1, + ver: '1.2' + }; + + const bidRequests = [{ + ...testBidRequest, + mediaTypes: { + native: { + ortb: nativeRequest + } + }, + nativeOrtbRequest: nativeRequest, + nativeParams: { + ortb: nativeRequest + } + }]; + const bidderRequest = Object.assign({}, testBidderRequest, { + bids: bidRequests + }); + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.satisfy(url => url.startsWith(ENDPOINT_URL_NORMAL)); + const data = request.data; + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].native.request).to.equal(JSON.stringify(nativeRequest)); + }); + } + }); + + describe('interpretResponse', function () { + const bannerBidderRequest = { + "bidderCode": "msft", + "auctionId": null, + "bidderRequestId": "f8c98171-d21f-4087-a1be-f72be8136dcc", + "bids": [{ + "bidder": "msft", + "params": { + "placement_id": 13144370 + }, + "ortb2Imp": { + "ext": { + "data": { + "adserver": { + "name": "gam", + "adslot": "/19968336/header-bid-tag-0" + } + }, + "gpid": "/19968336/header-bid-tag-0" + } + }, + "mediaTypes": { + "banner": { + "sizes": [ + [ + 300, + 250 + ] + ] + } + }, + "adUnitCode": "div-gpt-ad-1460505748561-0", + "transactionId": null, + "adUnitId": "94211d51-e391-4939-b965-bd8e974dca92", + "sizes": [ + [ + 300, + 250 + ] + ], + "bidId": "453e250c-a12c-420b-8539-ee0ef2f4868e", + "bidderRequestId": "f8c98171-d21f-4087-a1be-f72be8136dcc", + "auctionId": null, + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 1, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "ref": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true" + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 647 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Chromium", + "version": [ + "140" + ] + }, + { + "brand": "Not=A?Brand", + "version": [ + "24" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "140" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + } + }], + "auctionStart": 1759244033417, + "timeout": 1000, + "refererInfo": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "topmostLocation": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "location": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "canonicalUrl": null, + "page": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "domain": "test.localhost:9999", + "ref": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "referer": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "canonicalUrl": null + } + }, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true", + "ref": "http://test.localhost:9999/integrationExamples/gpt/hello_world_2.html?pbjs_debug=true" + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 647 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Chromium", + "version": [ + "140" + ] + }, + { + "brand": "Not=A?Brand", + "version": [ + "24" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "140" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + }, + "start": 1759244033424 + }; + + const bannerBidResponse = { + "body": { + "id": "099630d6-1943-43ef-841d-fe916871e00a", + "seatbid": [{ + "bid": [{ + "id": "2609670786764493419", + "impid": "453e250c-a12c-420b-8539-ee0ef2f4868e", + "price": 1.5, + "adid": "96846035", + "adm": "", + "adomain": [ + "prebid.org" + ], + "iurl": "https://nym2-ib.adnxs.com/cr?id=96846035", + "cid": "9325", + "crid": "96846035", + "h": 250, + "w": 300, + "ext": { + "appnexus": { + "brand_id": 1, + "auction_id": 5070987573008590000, + "bidder_id": 2, + "bid_ad_type": 0, + "buyer_line_item_id": 0, + "seller_line_item_id": 0, + "curator_line_item_id": 0, + "advertiser_id": 2529885, + "renderer_id": 0, + "no_ad_url": "https://nym2-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world_2.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKYDKAYBgAAAwDWAAUBCMTe78YGEObJ46zJivGvRhjGpKbEk569pU4qNgkAAAkCABEJBywAABkAAADA9Sj4PyEREgApEQkAMREb9A4BMLKiogY47UhA7UhIAFAAWJzxW2AAaLXPeXgAgAEBigEAkgEDVVNEmAGsAqAB-gGoAQGwAQC4AQLAAQDIAQLQAQDYAQDgAQDwAQCKAil1ZignYScsIDI1Mjk4ODUsIDApO3VmKCdyJywgOTY4NDYwMzUsIDApO5ICpQQhVm1BdHdnakt2Sm9kRU5PQmx5NFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUXNxS2lCbGdBWU00QmFBQndGSGdJZ0FFVWlBRUlrQUVCbUFFQm9BRUJxQUVEc0FFQXVRRy0wRXpGQUFENFA4RUJ2dEJNeFFBQS1EX0pBZmg3cVp5SWNQRV8yUUVBQUFBQUFBRHdQLUFCQVBVQgURKEpnQ0FLQUNBTFVDBRAETDAJCPBJTUFDQU1nQ0FOQUNBTmdDQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYm9EQ1U1WlRUSTZOakl5TnVBRHFrcUlCQUNRQkFDWUJBSEJCQUEFVwEBCHlRUQEHCQEYTmdFQVBFRQkNAQEgQ0lCZEl3cVFVAQ0gQUFBRHdQN0VGAQoJAQhEQkIdPwB5FSgMQUFBTjIoAABaLigAuDRBWHdrd253QmUzODJnTDRCZDIwbWdHQ0JnTlZVMFNJQmdDUUJnR1lCZ0NoQmdBAU40QUFQZ19xQVlCc2dZa0MddABFHQwARx0MAEkdDKB1QVlLLUFlNDB3ajRCNkxWQ1BnSDdkd0ktQWVvN0FqNEJfUDhDSUVJQQlqYEFBQUNJQ0FDUUNBQS6aApUBIUtSQXh5UWoyKQIkblBGYklBUW9BRDEwWEQ0UHpvSlRsbE5Nam8yTWpJMlFLcEtTEYkMUEFfVREMDEFBQVcdDABZHQwAYR0MAGMdDBBlQUNKQR0QeMICNWh0dHBzOi8vZG9jcy5wcmViaWQub3JnL2Rldi0BFPBhL2dldHRpbmctc3RhcnRlZC5odG1s2AL36QPgAq2YSOoCVWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2hlbGxvX3dvcmxkXzIFUvBGP3BianNfZGVidWc9dHJ1ZYADAIgDAZADAJgDFKADAaoDAkgAwAPYBMgDANgDAOADAOgDAPgDA4AEAJIEEi9vcGVucnRiMi8JwfBhanOYBACiBA4xMDAuMTQuMTYzLjI1MKgE_UOyBA4IABAAGAAgADAAOAJCALgEAMAEgNq4IsgEANIEDjkzMjUjTllNMjo2MjI22gQCCADgBADwBNOBly6IBQGYBQCgBf____8FA7ABqgUkMDk5NjMwZDYtMTk0My00M2VmLTg0MWQtZmU5MTY4NzFlMDBhwAUAyQWJtBTwP9IFCQkJDDQAANgFAeAFAfAFAfoFBAGXKJAGAJgGALgGAMEGCSMo8L_QBvUv2gYWChAJERkBAcVg4AYB8gYCCACABwGIBwCgBwHIB4j9BdIHDxViASYQIADaBwYBX_CBGADgBwDqBwIIAPAHkPWmA4oIYQpdAAABmZseoaBGX8RUlZjk5qh-YEbiixFeNKSwU942xVq95IWMpLMfZlV-kwZx7igi_tadimiKAcrhNH810Dec1tTfiroSFHftKanxAhowy564iuN_tWpE5xar7QwcEAGVCAAAgD-YCAHACADSCA2HMNoIBAgAIADgCADoCAA.&s=6644c05b4a0f8a14c7aae16b72c1408265651a7e" + } + } + }], + "seat": "9325" + }], + "bidid": "5488067055951399787", + "cur": "USD", + "ext": { + "tmaxrequest": 150 + } + }, + "headers": {} + }; + + const videoInstreamBidderRequest = { + "bidderCode": "msft", + "auctionId": null, + "bidderRequestId": "ba7c3a68-9f32-4fab-97dc-d016fcef290b", + "bids": [{ + "bidder": "msft", + "params": { + "placement_id": 31523633 + }, + "ortb2Imp": { + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm", + "application/vnd.apple.mpegurl", + "application/javascript" + ], + "startdelay": 0, + "protocols": [ + 2, + 3, + 7 + ], + "w": 640, + "h": 360, + "placement": 1, + "maxextended": -1, + "boxingallowed": 1, + "playbackmethod": [ + 3 + ], + "playbackend": 1, + "pos": 1, + "api": [ + 2 + ] + }, + "ext": { + "data": {} + } + }, + "mediaTypes": { + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm", + "application/vnd.apple.mpegurl", + "application/javascript" + ], + "protocols": [ + 2, + 3, + 7 + ], + "api": [ + 2 + ], + "h": 360, + "w": 640, + "maxextended": -1, + "boxingallowed": 1, + "playbackmethod": [ + 3 + ], + "playbackend": 1, + "pos": 1, + "playerSize": [ + [ + 640, + 360 + ] + ], + "context": "instream", + "placement": 1, + "startdelay": 0 + } + }, + "adUnitCode": "div-gpt-ad-51545-0", + "transactionId": null, + "adUnitId": "b88648c1-fb3c-475e-bc44-764d12dbf4d8", + "sizes": [ + [ + 640, + 360 + ] + ], + "bidId": "8d37414a-7a4f-4f3b-a922-5e9db77a6d6c", + "bidderRequestId": "ba7c3a68-9f32-4fab-97dc-d016fcef290b", + "auctionId": null, + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 1, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "ref": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "content": { + "url": "" + } + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 647 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Chromium", + "version": [ + "140" + ] + }, + { + "brand": "Not=A?Brand", + "version": [ + "24" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "140" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + } + }], + "auctionStart": 1759252766012, + "timeout": 3000, + "refererInfo": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "topmostLocation": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "location": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "canonicalUrl": null, + "page": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "domain": "test.localhost:9999", + "ref": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "referer": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "canonicalUrl": null + } + }, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "ref": "http://test.localhost:9999/integrationExamples/videoModule/videojs/localVideoCache.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member_id=13859", + "content": { + "url": "" + } + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 647 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Chromium", + "version": [ + "140" + ] + }, + { + "brand": "Not=A?Brand", + "version": [ + "24" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "140" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + }, + "start": 1759252766017 + }; + + const videoInstreamBidResponse = { + "body": { + "id": "e999d11a-38f8-46e3-84ec-55103f10e760", + "seatbid": [{ + "bid": [{ + "id": "6400954803477699288", + "impid": "8d37414a-7a4f-4f3b-a922-5e9db77a6d6c", + "price": 10, + "adid": "484626808", + "adm": "adnxs", + "adomain": [ + "example.com" + ], + "iurl": "https://nym2-ib.adnxs.com/cr?id=484626808", + "nurl": "https://nym2-ib.adnxs.com/something?", + "cid": "13859", + "crid": "484626808", + "h": 1, + "w": 1, + "ext": { + "appnexus": { + "brand_id": 1, + "auction_id": 3335251835858264600, + "bidder_id": 2, + "bid_ad_type": 1, + "creative_info": { + "video": { + "duration": 30, + "mimes": [ + "video/mp4" + ] + } + }, + "buyer_line_item_id": 0, + "seller_line_item_id": 0, + "curator_line_item_id": 0, + "advertiser_id": 6621028, + "renderer_id": 0, + "no_ad_url": "https://nym2-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2FvideoModule%2Fvideojs%2FlocalVideoCache.html%3Fpbjs_debug%3Dtrue%26apn_debug_dongle%3DQWERTY%26apn_debug_member_id%3D13859&e=wqT_3QLTDKBTBgAAAwDWAAUBCM-i8MYGEPvDtIb7u8ykLhjGpKbEk569pU4qNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwdeA_MLGGhA84o2xAo2xIAFAAWJWvogFgAGidjcYBeACAAQGKAQCSAQNVU0SYAQGgAQGoAQGwAQC4AQPAAQDIAQLQAQDYAQDgAQDwAQCKAj51ZignYScsIDY2MjEwMjgsIDApO3VmKCdpJywgNzY1ODIzNywgMCkFFDByJywgNDg0NjI2ODA4BRbwi5ICuQQhcUdYT1pnajhySVFjRVBpaWktY0JHQUFnbGEtaUFUQUFPQUJBQkVpamJGQ3hob1FQV0FCZ3pnRm9BSEFBZUFDQUFRQ0lBUUNRQVFHWUFRR2dBUUdvQVFHd0FRQzVBWlVjNEVBMFA0OUF3UUh6cldxa0FBQWtRTWtCQUFBQUFBQUE4RF9aQVFBCQ50UEFfNEFIOXRkTUQ5UUVBQUNCQm1BSUFvQUlCdFFJBSQAdg0I8FV3QUlBeUFJQTBBSUEyQUlBNEFJQTZBSUEtQUlBZ0FNQm1BTUJ1Z01KVGxsTk1qbzBOVFkwNEFPcVNvQUU2TG1yQ1lnRWh2VGxESkFFQUpnRUFjRUVBQQVjFEFBQURKQgEHDQEYMkFRQThRUQ0OKEFBQUlnRjFDT3BCERMUUEFfc1FVARoJAQhNRUYJCRRBQUpFREoVKAxBQUEwBSggQkFNei1QUU5rFShIOERfZ0JjQ0VQZkFGNUk2b0NfZwEIYFVBNElHQTFWVFJJZ0dBSkFHQVpnR0FLRUcBTAEBLEpFQ29CZ1N5QmlRSgEQDQEAUg0IAQEAWgEFDQEAaA0IgEFBQUM0QmlqNEI3alRDUGdIb3RVSS1BZnQzQWo0QjZqcwEUGDhfd0lnUWcBLQEBXGtRSWdJQUpBSUFBLi6aApkBIWhoR1U5dzo9AixKV3ZvZ0VnQkNnQU0xIVRDUkFPZ2xPV1UweU9qUTFOalJBcWtwFbEIOEQ5HbEAQh2xAEIdsQRCcAGGCQEEQngJCAEBEEI0QUlrNbDwUjhEOC7YAgDgAuSPXeoCmQFodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL3ZpZGVvTW9kdWxlL3ZpZGVvanMvBTeIVmlkZW9DYWNoZS5odG1sP3BianNfZGVidWc9dHJ1ZSZhcG4JDzRfZG9uZ2xlPVFXRVJUWR0Y9CoBbWVtYmVyX2lkPTEzODU5gAMAiAMBkAMAmAMUoAMBqgMCSADAA-CoAcgDANgDAOADAOgDAPgDA4AEAJIEEi9vcGVucnRiMi9wcmViaWRqc5gEAaIEDjEwMC4xNC4xNjMuMjUwqASORbIEEggBEAgYgAUg6AIoAjAAOARCALgEAMAEAMgEANIEDzEzODU5I05ZTTI6NDU2NNoEAggA4AQA8AT4oovnAYgFAZgFAKAF____________AaoFJGU5OTlkMTFhLTM4ZjgtNDZlMy04NGVjLTU1MTAzZjEwZTc2MMAFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBQH6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwv9AG2OYD2gYWChAAAAAAAAABRQkBbBAAGADgBgTyBgIIAIAHAYgHAKAHQMgHANIHDwkJIgAABSQUIADaBwYIBQvwk-AHAOoHAggA8AeQ9aYDighhCl0AAAGZm6OcmC5JMd-wzSH7S9CRaxvHzflX566DLUSOdQz88wyj3PqZEziVi4kwgLfD1XTpdj9BkTddNkqxU3TRdaKoURBAeRFiz3Ky5sh4Ali0fl6qRX1x8G-p788QAZUIAACAP5gIAcAIANIIBggAEAAYANoIBAgAIADgCADoCAA.&s=20f85682f8ef5755702e4b1bc90549390e5b580a", + "asset_url": "https://nym2-ib.adnxs.com/ab?ro=1&an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2FvideoModule%2Fvideojs%2FlocalVideoCache.html%3Fpbjs_debug%3Dtrue%26apn_debug_dongle%3DQWERTY%26apn_debug_member_id%3D13859&e=wqT_3QLpDqBpBwAAAwDWAAUBCM-i8MYGEPvDtIb7u8ykLhjGpKbEk569pU4qNgkAAAECCCRAEQEHEAAAJEAZCQkI4D8hCQkIJEApEQkAMQkJsOA_MLGGhA84o2xAo2xIAlD4oovnAViVr6IBYABonY3GAXgAgAEBigEDVVNEkgUG8EaYAQGgAQGoAQGwAQC4AQPAAQTIAQLQAQDYAQDgAQDwAQCKAj51ZignYScsIDY2MjEwMjgsIDApO3VmKCdpJywgNzY1ODIzNxUUMHInLCA0ODQ2MjY4MDgFFvCLkgK5BCFxR1hPWmdqOHJJUWNFUGlpaS1jQkdBQWdsYS1pQVRBQU9BQkFCRWlqYkZDeGhvUVBXQUJnemdGb0FIQUFlQUNBQVFDSUFRQ1FBUUdZQVFHZ0FRR29BUUd3QVFDNUFaVWM0RUEwUDQ5QXdRSHpyV3FrQUFBa1FNa0JBQUFBQUFBQThEX1pBUUEJDnRQQV80QUg5dGRNRDlRRUFBQ0JCbUFJQW9BSUJ0UUkFJAB2DQjwVXdBSUF5QUlBMEFJQTJBSUE0QUlBNkFJQS1BSUFnQU1CbUFNQnVnTUpUbGxOTWpvME5UWTA0QU9xU29BRTZMbXJDWWdFaHZUbERKQUVBSmdFQWNFRUFBBWMUQUFBREpCAQcNARgyQVFBOFFRDQ4oQUFBSWdGMUNPcEIRExRQQV9zUVUBGgkBCE1FRgkJFEFBSkVEShUoDEFBQTAFKCBCQU16LVBRTmsVKEg4RF9nQmNDRVBmQUY1STZvQ19nAQhgVUE0SUdBMVZUUklnR0FKQUdBWmdHQUtFRwFMAQEsSkVDb0JnU3lCaVFKARANAQBSDQgBAQBaAQUNAQBoDQiAQUFBQzRCaWo0QjdqVENQZ0hvdFVJLUFmdDNBajRCNmpzARQYOF93SWdRZwEtAQFca1FJZ0lBSkFJQUEuLpoCmQEhaGhHVTl3Oj0CLEpXdm9nRWdCQ2dBTTEhVENSQU9nbE9XVTB5T2pRMU5qUkFxa3AVsQg4RDkdsQBCHbEAQh2xBEJwAYYJAQRCeAkIAQEQQjRBSWs1sPBSOEQ4LtgCAOAC5I9d6gKZAWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvdmlkZW9Nb2R1bGUvdmlkZW9qcy8FN4hWaWRlb0NhY2hlLmh0bWw_cGJqc19kZWJ1Zz10cnVlJmFwbgkPNF9kb25nbGU9UVdFUlRZHRhwbWVtYmVyX2lkPTEzODU58gIRCgZBRFZfSUQSBzZpwhzyAhIKBkNQRwEUPAgyMzcyNTkyNPICCgoFQ1ABFBgBMPICDQoIATYMRlJFUREQHFJFTV9VU0VSBRAADAkgGENPREUSAPIBDwFREQ8QCwoHQ1AVDhAQCgVJTwFZBAc3iS8A8gEhBElPFSE4EwoPQ1VTVE9NX01PREVMASsUAPICGgoWMhYAHExFQUZfTkFNBXEIHgoaNh0ACEFTVAE-EElGSUVEAT4cDQoIU1BMSVQBTfDlATCAAwCIAwGQAwCYAxSgAwGqAwJIAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQSL29wZW5ydGIyL3ByZWJpZGpzmAQBogQOMTAwLjE0LjE2My4yNTCoBI5FsgQSCAEQCBiABSDoAigCMAA4BEIAuAQAwAQAyAQA0gQPMTM4NTkjTllNMjo0NTY02gQCCAHgBADwBPiii-cBiAUBmAUAoAX___________8BqgUkZTk5OWQxMWEtMzhmOC00NmUzLTg0ZWMtNTUxMDNmMTBlNzYwwAUAyQUAAAAAAADwP9IFCQkAAAAFDmjYBQHgBQHwBQH6BQQIABAAkAYBmAYAuAYAwQYFIDAA8D_QBtjmA9oGFgoQCRIZAWwQABgA4AYE8gYCCACABwGIBwCgB0DIBwDSBw8JESYBJBAgANoHBgFe8J0YAOAHAOoHAggA8AeQ9aYDighhCl0AAAGZm6OcmC5JMd-wzSH7S9CRaxvHzflX566DLUSOdQz88wyj3PqZEziVi4kwgLfD1XTpdj9BkTddNkqxU3TRdaKoURBAeRFiz3Ky5sh4Ali0fl6qRX1x8G-p788QAZUIAACAP5gIAcAIANIIDgiBgoSIkKDAgAEQABgA2ggECAAgAOAIAOgIAA..&s=ec6b67f896520314ab0b7fdb4b0847a14df44537{AUCTION_PRICE}" + } + } + }], + "seat": "13859" + }], + "bidid": "3531514400060956584", + "cur": "USD", + "ext": { + "tmaxrequest": 150 + } + }, + "headers": {} + }; + + const videoOutstreamBidderRequest = { + "bidderCode": "msft", + "auctionId": null, + "bidderRequestId": "3348a473-ad23-4672-bd82-cb0625b1ccd5", + "bids": [{ + "bidder": "msft", + "params": { + "placement_id": 33911093, + "video": { + "skippable": true + } + }, + "ortb2Imp": { + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 640, + "h": 480, + "plcmt": 4 + }, + "ext": { + "data": { + "adserver": { + "name": "gam", + "adslot": "/19968336/prebid_outstream_adunit_1" + } + }, + "gpid": "/19968336/prebid_outstream_adunit_1" + } + }, + "mediaTypes": { + "video": { + "playerSize": [ + [ + 640, + 480 + ] + ], + "context": "outstream", + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "plcmt": 4, + "w": 640, + "h": 480 + } + }, + "adUnitCode": "video1", + "transactionId": null, + "adUnitId": "202e3ff9-e9fc-4b91-84d8-c808e7f8f1b2", + "sizes": [ + [ + 640, + 480 + ] + ], + "bidId": "29ffa2b1-821d-4542-b948-8533c1832a68", + "bidderRequestId": "3348a473-ad23-4672-bd82-cb0625b1ccd5", + "auctionId": null, + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 1, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/gpt/old/outstream.html?pbjs_debug=true" + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 815 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Google Chrome", + "version": [ + "141" + ] + }, + { + "brand": "Not?A_Brand", + "version": [ + "8" + ] + }, + { + "brand": "Chromium", + "version": [ + "141" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + } + }], + "auctionStart": 1759325217458, + "timeout": 3000, + "refererInfo": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "topmostLocation": "http://test.localhost:9999/integrationExamples/gpt/old/outstream.html?pbjs_debug=true", + "location": "http://test.localhost:9999/integrationExamples/gpt/old/outstream.html?pbjs_debug=true", + "canonicalUrl": null, + "page": "http://test.localhost:9999/integrationExamples/gpt/old/outstream.html?pbjs_debug=true", + "domain": "test.localhost:9999", + "ref": null, + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "referer": "http://test.localhost:9999/integrationExamples/gpt/old/outstream.html?pbjs_debug=true", + "canonicalUrl": null + } + }, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/gpt/old/outstream.html?pbjs_debug=true" + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 815 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Google Chrome", + "version": [ + "141" + ] + }, + { + "brand": "Not?A_Brand", + "version": [ + "8" + ] + }, + { + "brand": "Chromium", + "version": [ + "141" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + }, + "start": 1759325217463 + } + + const videoOutstreamBidResponse = { + "body": { + "id": "cb624440-f8bd-4da1-8256-d8a243651bef", + "seatbid": [{ + "bid": [{ + "id": "3757141233787776626", + "impid": "29ffa2b1-821d-4542-b948-8533c1832a68", + "price": 25.00001, + "adid": "546521568", + "adm": "adnxs", + "adomain": [ + "example.com" + ], + "iurl": "https://nym2-ib.adnxs.com/cr?id=546521568", + "nurl": "https://nym2-ib.adnxs.com/something", + "cid": "7877", + "crid": "546521568", + "h": 1, + "w": 1, + "ext": { + "appnexus": { + "brand_id": 1, + "auction_id": 9005203323134521000, + "bidder_id": 2, + "bid_ad_type": 1, + "creative_info": { + "video": { + "duration": 30, + "mimes": [ + "video/mp4", + "video/webm" + ] + } + }, + "buyer_line_item_id": 0, + "seller_line_item_id": 0, + "curator_line_item_id": 0, + "advertiser_id": 10896419, + "renderer_id": 2, + "renderer_url": "https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js", + "renderer_config": "{}", + "no_ad_url": "https://nym2-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fold%2Foutstream.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKJDHwJBgAAAwDWAAUBCK_Y9MYGEJPj5qzflrr8fBgAKjYJAA0BABENCAQAGQkJCOA_IQkJCAAAKREJADEJCfSoAeA_MLXilRA4xT1AxT1IAFAAWIKjsQFgAGjIgtUBeACAAQGKAQCSAQNVU0SYAQGgAQGoAQGwAQC4AQPAAQDIAQLQAQDYAQDgAQDwAQCKAkB1ZignYScsIDEwODk2NDE5LCAwKTt1ZignaScsIDEwNTkyNDIwLCAwKTt1ZigncicsIDU0NjUyMTU2OCwgMCk7kgK9BCEyMmNxTndpcHI3a2RFT0NEellRQ0dBQWdncU94QVRBQU9BQkFCRWpGUFZDMTRwVVFXQUJnX19fX193OW9BSEFXZUFDQUFSYUlBUUNRQVFHWUFRR2dBUUdvQVFHd0FRQzVBWEJaaGMwQUFEbEF3UUZ3V1lYTkFBQTVRTWtCQUFBQUFBQUE4RF9aQVFBQUFBQUFBUEFfNEFHa3dZWUY5UUVBQU1oQm1BSUFvQUlCdFFJQUFBQUF2UUlBQUFBQXdBSUJ5QUlCMEFJVzJBSUE0QUlBNkFJQS1BSUFnQU1CbUFNQnVnTUpUbGxOTWpvME9UUXg0QU9yU29BRWdObUtENGdFdy1DS0Q1QUVBSmdFQWNFRUFBQUFBAYgIQURKFaEkQUFBMkFRQThRUQELCQEcSWdGelNhcEIRExRQQV9zUVUJHAEBCE1FRgEHAQEMT1VESi4oAAAwLigABE5rFSjIOERfZ0JhSExtQUh3QllQdTNRejRCYU9JbVFXQ0JnTlZVMFNJQmdDUUJnR1lCZ0NoQmdBAV80QUFEbEFxQVlFc2dZa0MRkAxBQUFFHQwARx0MAEkdDKB1QVlVLUFlNDB3ajRCNkxWQ1BnSDdkd0ktQWVvN0FqNEJfUDhDSUVJQQFRaEFBQU9VQ0lDQUNRQ0FBLpoCmQEhR2hHSHlnaTZBAixJS2pzUUVnQkNnQU0RbVhEbEFPZ2xPV1UweU9qUTVOREZBcTBwSgFVAQEMOEQ5UgEICQEEQloJCAEBBEJoAQYJAQRCcAkIAQEEQngBBgkBEEI0QUlrNbD0NAE4RDgu2AIA4ALUxj3qAlVodHRwOi8vdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9vbGQvb3V0c3RyZWFtLmh0bWw_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMUoAMBqgMCSADAA-CoAcgDANgDAOADAOgDAPgDA4AEAJIEEi9vcGVucnRiMi9wcmViaWRqc5gEAKIEDjEwMC4xNC4xNjMuMjUwqAQAsgQQCAAQABiABSDgAzAAOARCALgEAMAEAMgEANIEDjc4NzcjTllNMjo0OTQx2gQCCADgBADwBOCDzYQCiAUBmAUAoAX___________8BqgUkY2I2MjQ0NDAtZjhiZC00ZGExLTgyNTYtZDhhMjQzNjUxYmVmwAUAyQWJpBDwP9IFCZXdaNgFAeAFAfAFAfoFBAgAEACQBgGYBgC4BgDBBg0vJL_QBqIo2gYWChAJERkBcBAAGADgBgTyBgIIAIAHAYgHAKAHQMgHybsF0gcPFWIBJhAgANoHBgFf8IEYAOAHAOoHAggA8AeQ9aYDighhCl0AAAGZn_SXmHz46LX1mbGTFPjBc4ofoClrarilv48ccB0T3Vm-FTukoSSDehJCIeSY21q6N-oSr0ocUA3idwnaOplNcuHDF9VJLxBvM58E-tcQVhuo1F41W8_LM1AQAZUIAACAP5gIAcAIANIIDYcw2ggECAAgAOAIAOgIAA..&s=ce270f0cb1dee88fbb6b6bb8d59b1d9ca7e38e90" + } + } + }], + "seat": "7877" + }], + "bidid": "1510787988993274243", + "cur": "USD", + "ext": { + "tmaxrequest": 150 + } + }, + "headers": {} + } + + const nativeBidderRequest = { + "bidderCode": "msft", + "auctionId": null, + "bidderRequestId": "cdfd0842-275b-4f87-8b46-8f4052454a5e", + "bids": [{ + "bidder": "msft", + "params": { + "placement_id": 33907873 + }, + "ortb2Imp": { + "ext": { + "data": { + "adserver": { + "name": "gam", + "adslot": "/19968336/prebid_native_cdn_test_1" + } + }, + "gpid": "/19968336/prebid_native_cdn_test_1" + } + }, + "nativeParams": { + "ortb": { + "ver": "1.2", + "assets": [{ + "id": 1, + "required": 1, + "img": { + "type": 3, + "w": 989, + "h": 742 + } + }, + { + "id": 2, + "required": 1, + "title": { + "len": 100 + } + }, + { + "id": 3, + "required": 1, + "data": { + "type": 1 + } + } + ] + } + }, + "nativeOrtbRequest": { + "ver": "1.2", + "assets": [{ + "id": 1, + "required": 1, + "img": { + "type": 3, + "w": 989, + "h": 742 + } + }, + { + "id": 2, + "required": 1, + "title": { + "len": 100 + } + }, + { + "id": 3, + "required": 1, + "data": { + "type": 1 + } + } + ] + }, + "mediaTypes": { + "native": { + "ortb": { + "ver": "1.2", + "assets": [{ + "id": 1, + "required": 1, + "img": { + "type": 3, + "w": 989, + "h": 742 + } + }, + { + "id": 2, + "required": 1, + "title": { + "len": 100 + } + }, + { + "id": 3, + "required": 1, + "data": { + "type": 1 + } + } + ] + } + } + }, + "adUnitCode": "/19968336/prebid_native_cdn_test_1", + "transactionId": null, + "adUnitId": "e93238c6-03b8-4142-bd2b-af384da2b0ae", + "sizes": [], + "bidId": "519ca815-b76b-4ab0-9dc5-c78fa77dd7b1", + "bidderRequestId": "cdfd0842-275b-4f87-8b46-8f4052454a5e", + "auctionId": null, + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 1, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "ref": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325" + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 647 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Chromium", + "version": [ + "140" + ] + }, + { + "brand": "Not=A?Brand", + "version": [ + "24" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "140" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + } + }], + "auctionStart": 1759249513048, + "timeout": 3000, + "refererInfo": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "topmostLocation": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "location": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "canonicalUrl": null, + "page": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "domain": "test.localhost:9999", + "ref": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "referer": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "canonicalUrl": null + } + }, + "ortb2": { + "site": { + "domain": "test.localhost:9999", + "publisher": { + "domain": "test.localhost:9999" + }, + "page": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325", + "ref": "http://test.localhost:9999/integrationExamples/gpt/old/demo_native_cdn.html?pbjs_debug=true&apn_debug_dongle=QWERTY&apn_debug_member=9325" + }, + "device": { + "w": 2560, + "h": 1440, + "dnt": 0, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", + "language": "en", + "ext": { + "vpw": 2560, + "vph": 647 + }, + "sua": { + "source": 1, + "platform": { + "brand": "macOS" + }, + "browsers": [{ + "brand": "Chromium", + "version": [ + "140" + ] + }, + { + "brand": "Not=A?Brand", + "version": [ + "24" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "140" + ] + } + ], + "mobile": 0 + } + }, + "source": {} + }, + "start": 1759249513055 + }; + + const nativeBidResponse = { + "body": { + "id": "408873b5-0b75-43f2-b490-ba05466265e7", + "seatbid": [{ + "bid": [{ + "id": "2634147710021988035", + "impid": "519ca815-b76b-4ab0-9dc5-c78fa77dd7b1", + "price": 5, + "adid": "546255182", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"img\":{\"url\":\"https:\\/\\/crcdn01.adnxs-simple.com\\/creative20\\/p\\/9325\\/2024\\/8\\/14\\/60018074\\/6ceb0f95-1465-4e90-b295-4b6e2aff3035.jpg\",\"w\":989,\"h\":742,\"ext\":{\"appnexus\":{\"prevent_crop\":0}}}},{\"id\":2,\"title\":{\"text\":\"This is a AST Native Creative\"}},{\"id\":3,\"data\":{\"value\":\"AST\"}}],\"link\":{\"url\":\"https:\\/\\/nym2-ib.adnxs.com\\/click2?e=wqT_3QKfAfBDnwAAAAMAxBkFAQizifDGBhD8vNTpipOK8TEYxqSmxJOevaVOIKHJlRAo7Ugw7Ug4AkDO4ryEAkijlKIBUABaA1VTRGIBBWhoAXABeJ7txQGAAQCIAQGQAQKYAQSgAQKpAQAFAQgUQLEVCgC5DQoI4D_BDQoIFEDJFQow2AEA4AEA8AH1L_gBAA..\\/s=fec918f3f3660ce11dc2975bb7beb9df3d181748\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21khGz_Qi-7rgdEM7ivIQCGKOUogEgBCgAMQAAAAAAABRAOglOWU0yOjQ5ODlAqkpJAAAAAAAA8D9RAAAAAAAAAABZAAAAAAAAAABhAAAAAAAAAABpAAAAAAAAAABxAAAAAAAAAAB4AIkBAAAAAAAA8D8.\\/cca=OTMyNSNOWU0yOjQ5ODk=\\/bn=0\\/clickenc=http%3A%2F%2Fprebid.org\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"https:\\/\\/nym2-ib.adnxs.com\\/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fold%2Fdemo_native_cdn.html%3Fpbjs_debug%3Dtrue%26apn_debug_dongle%3DQWERTY%26apn_debug_member%3D9325&e=wqT_3QLSDKBSBgAAAwDWAAUBCLOJ8MYGEPy81OmKk4rxMRjGpKbEk569pU4qNgkAAAECCBRAEQEHEAAAFEAZCQkI4D8hCQkIFEApEQkAMQkJsOA_MKHJlRA47UhA7UhIAlDO4ryEAlijlKIBYABonu3FAXgAgAEBigEDVVNEkgUG8EaYAQGgAQGoAQGwAQC4AQLAAQTIAQLQAQDYAQDgAQDwAQCKAj51ZignYScsIDY1NjgyOTEsIDApO3VmKCdpJywgNzYyNDE2NRUUMHInLCA1NDYyNTUxODIFFvCQkgK9BCF3Mmd1QmdpLTdyZ2RFTTdpdklRQ0dBQWdvNVNpQVRBQU9BQkFCRWp0U0ZDaHlaVVFXQUJnemdGb0FIQU9lTFlZZ0FFT2lBRzJHSkFCQVpnQkFhQUJBYWdCQWJBQkFMa0I4NjFxcEFBQUZFREJBZk90YXFRQUFCUkF5UUVBQUFBQUFBRHdQOWtCQUFBQQEPdDhEX2dBZVdyMFFQMUFRQUFvRUNZQWdDZ0FnRzFBZwEiBEM5CQjwVURBQWdESUFnRFFBZzdZQXJZWTRBSUE2QUlBLUFJQWdBTUJtQU1CdWdNSlRsbE5Nam8wT1RnNTRBT3FTb0FFdXM2Z0NZZ0VrZnFKRDVBRUFKZ0VBY0VFAWIJAQREShWVJEFBQTJBUUE4UVEBCwkBHElnRl9TYXBCERMUUEFfc1FVCRwBAQhNRUYBBwEBDEZFREouKAAAMC4oAAROaxUoAfywQmFEQ0h2QUYyYXZkRFBnRjRfS1FBNElHQTFWVFJJZ0dBSkFHQVpnR0FLRUdBAV0lXCRDb0JnU3lCaVFKARANAQBSDQgBAQBaAQUNAQBoDQiAQUFBQzRCZ3I0QjdqVENQZ0hvdFVJLUFmdDNBajRCNmpzARQUOF93SWdRJXkBAVxVUUlnSUFKQUlBQS4umgKZASFraEd6X1E6QQIsS09Vb2dFZ0JDZ0FNMSFUQlJBT2dsT1dVMHlPalE1T0RsQXFrcBWxCDhEOR2xAEIdsQBCHbEEQnABhgkBBEJ4CQgBARBCNEFJazWw9LYBOEQ4LtgC9-kD4AKtmEjqAokBaHR0cDovL3Rlc3QubG9jYWxob3N0Ojk5OTkvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvb2xkL2RlbW9fbmF0aXZlX2Nkbi5odG1sP3BianNfZGVidWc9dHJ1ZSZhcG5fZGVidWdfZG9uZ2xlPVFXRVJUWSZhcG5fZGVidWdfbWVtYmVyPTkzMjWAAwCIAwGQAwCYAxSgAwGqAwJIAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQSL29wZW5ydGIyL3ByZWJpZGpzmAQAogQOMTAwLjE0LjE2My4yNTCoBNhEsgQOCAAQABgAIAAwADgAQgC4BADABADIBADSBA45MzI1I05ZTTI6NDk4OdoEAggB4AQA8ATO4ryEAogFAZgFAKAF____________AaoFJDQwODg3M2I1LTBiNzUtNDNmMi1iNDkwLWJhMDU0NjYyNjVlN8AFAMkFAAAAAAAA8D_SBQkJAAAAAAAAAADYBQHgBQHwBQH6BQQIABAAkAYBmAYAuAYAwQYAAAAAAADwP9AG9S_aBhYKEAAAAAAAAAAAAAAAAAEbaBAAGADgBgzyBgIIAIAHAYgHAKAHQcgHANIHDxVgASQQIADaBwYJ8vCb4AcA6gcCCADwB5D1pgOKCGEKXQAAAZmbcls4MeIomK01HnxjZ19jnODCYNG_e0eXMrsJyOA5um4JVppxvM9079B8pwi2cU2gbzDjYgmYgkdUJXwe4yn9EtYSYNavJIDFeQm0RRGvDEj6ltcLGUilABABlQgAAIA_mAgBwAgA0ggOCIGChIiQoMCAARAAGADaCAQIACAA4AgA6AgA&s=0a66129aafb703cfab8bbce859eacdfa0f456a28&pp=${AUCTION_PRICE}\"}]}", + "adomain": [ + "example.com" + ], + "iurl": "https://nym2-ib.adnxs.com/cr?id=546255182", + "cid": "9325", + "crid": "546255182", + "ext": { + "appnexus": { + "brand_id": 1, + "auction_id": 3594480088801156600, + "bidder_id": 2, + "bid_ad_type": 3, + "buyer_line_item_id": 0, + "seller_line_item_id": 0, + "curator_line_item_id": 0, + "advertiser_id": 6568291, + "renderer_id": 0, + "no_ad_url": "https://nym2-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fold%2Fdemo_native_cdn.html%3Fpbjs_debug%3Dtrue%26apn_debug_dongle%3DQWERTY%26apn_debug_member%3D9325&e=wqT_3QLDDKBDBgAAAwDWAAUBCLOJ8MYGEPy81OmKk4rxMRjGpKbEk569pU4qNgkAAAkCABEJBwgAABkJCQjgPyEJCQgAACkRCQAxCQnwdeA_MKHJlRA47UhA7UhIAFAAWKOUogFgAGie7cUBeACAAQGKAQCSAQNVU0SYAQGgAQGoAQGwAQC4AQLAAQDIAQLQAQDYAQDgAQDwAQCKAj51ZignYScsIDY1NjgyOTEsIDApO3VmKCdpJywgNzYyNDE2NSwgMCkFFDByJywgNTQ2MjU1MTgyBRbwkJICvQQhdzJndUJnaS03cmdkRU03aXZJUUNHQUFnbzVTaUFUQUFPQUJBQkVqdFNGQ2h5WlVRV0FCZ3pnRm9BSEFPZUxZWWdBRU9pQUcyR0pBQkFaZ0JBYUFCQWFnQkFiQUJBTGtCODYxcXBBQUFGRURCQWZPdGFxUUFBQlJBeVFFQUFBQUFBQUR3UDlrQkFBQUEBD3Q4RF9nQWVXcjBRUDFBUUFBb0VDWUFnQ2dBZ0cxQWcBIgRDOQkI8FVEQUFnRElBZ0RRQWc3WUFyWVk0QUlBNkFJQS1BSUFnQU1CbUFNQnVnTUpUbGxOTWpvME9UZzU0QU9xU29BRXVzNmdDWWdFa2ZxSkQ1QUVBSmdFQWNFRQFiCQEEREoVlSRBQUEyQVFBOFFRAQsJARxJZ0ZfU2FwQhETFFBBX3NRVQkcAQEITUVGAQcBAQxGRURKLigAADAuKAAETmsVKAH8sEJhRENIdkFGMmF2ZERQZ0Y0X0tRQTRJR0ExVlRSSWdHQUpBR0FaZ0dBS0VHQQFdJVwkQ29CZ1N5QmlRSgEQDQEAUg0IAQEAWgEFDQEAaA0IgEFBQUM0QmdyNEI3alRDUGdIb3RVSS1BZnQzQWo0QjZqcwEUFDhfd0lnUSV5AQFcVVFJZ0lBSkFJQUEuLpoCmQEha2hHel9ROkECLEtPVW9nRWdCQ2dBTTEhVEJSQU9nbE9XVTB5T2pRNU9EbEFxa3AVsQg4RDkdsQBCHbEAQh2xBEJwAYYJAQRCeAkIAQEQQjRBSWs1sPS2AThEOC7YAvfpA-ACrZhI6gKJAWh0dHA6Ly90ZXN0LmxvY2FsaG9zdDo5OTk5L2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L29sZC9kZW1vX25hdGl2ZV9jZG4uaHRtbD9wYmpzX2RlYnVnPXRydWUmYXBuX2RlYnVnX2RvbmdsZT1RV0VSVFkmYXBuX2RlYnVnX21lbWJlcj05MzI1gAMAiAMBkAMAmAMUoAMBqgMCSADAA-CoAcgDANgDAOADAOgDAPgDA4AEAJIEEi9vcGVucnRiMi9wcmViaWRqc5gEAKIEDjEwMC4xNC4xNjMuMjUwqATYRLIEDggAEAAYACAAMAA4AEIAuAQAwAQAyAQA0gQOOTMyNSNOWU0yOjQ5ODnaBAIIAOAEAPAEzuK8hAKIBQGYBQCgBf___________wGqBSQ0MDg4NzNiNS0wYjc1LTQzZjItYjQ5MC1iYTA1NDY2MjY1ZTfABQDJBQAAAAAAAPA_0gUJCQAAAAAAAAAA2AUB4AUB8AUB-gUECAAQAJAGAZgGALgGAMEGAAAAAAAA8L_QBvUv2gYWChAAAAAAAAAAAAAAAAABG2gQABgA4AYM8gYCCACABwGIBwCgB0HIBwDSBw8VYAEkECAA2gcGCfLwk-AHAOoHAggA8AeQ9aYDighhCl0AAAGZm3JbODHiKJitNR58Y2dfY5zgwmDRv3tHlzK7CcjgObpuCVaacbzPdO_QfKcItnFNoG8w42IJmIJHVCV8HuMp_RLWEmDWrySAxXkJtEURrwxI-pbXCxlIpQAQAZUIAACAP5gIAcAIANIIBggAEAAYANoIBAgAIADgCADoCAA.&s=98218facb1e5673b9630690b1a1b943ce1e978de" + } + } + }], + "seat": "9325" + }], + "bidid": "5186086519274374393", + "cur": "USD", + "ext": { + "tmaxrequest": 150 + } + }, + "headers": {} + }; + + it('should interpret a banner response', function () { + const request = spec.buildRequests(bannerBidderRequest.bids, bannerBidderRequest)[0]; + const bids = spec.interpretResponse(bannerBidResponse, request); + + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.mediaType).to.equal(BANNER); + expect(bid.cpm).to.equal(1.5); + expect(bid.ad).to.equal(bannerBidResponse.body.seatbid[0].bid[0].adm); + expect(bid.meta.advertiser_id).to.equal(2529885); + }); + + if (FEATURES.VIDEO) { + it('should interpret a video instream response', function () { + const request = spec.buildRequests(videoInstreamBidderRequest.bids, videoInstreamBidderRequest)[0]; + const bids = spec.interpretResponse(videoInstreamBidResponse, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.cpm).to.equal(10); + expect(bid.vastUrl).to.equal(`${videoInstreamBidResponse.body.seatbid[0].bid[0].nurl}&redir=${encodeURIComponent(videoInstreamBidResponse.body.seatbid[0].bid[0].ext.appnexus.asset_url)}`); + expect(bid.playerWidth).to.equal(640); + expect(bid.playerHeight).to.equal(360); + expect(bid.meta.advertiser_id).to.equal(6621028); + }); + + it('should interpret a video outstream response', function () { + const request = spec.buildRequests(videoOutstreamBidderRequest.bids, videoOutstreamBidderRequest)[0]; + const bids = spec.interpretResponse(videoOutstreamBidResponse, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.cpm).to.equal(25.00001); + expect(bid.vastXml).to.equal(videoOutstreamBidResponse.body.seatbid[0].bid[0].adm); + expect(bid.playerWidth).to.equal(640); + expect(bid.playerHeight).to.equal(480); + expect(bid.meta.advertiser_id).to.equal(10896419); + expect(typeof bid.renderer.render).to.equal('function'); + }); + } + + if (FEATURES.NATIVE) { + it('should interpret a native response', function () { + const request = spec.buildRequests(nativeBidderRequest.bids, nativeBidderRequest)[0]; + const bids = spec.interpretResponse(nativeBidResponse, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.mediaType).to.equal(NATIVE); + expect(bid.cpm).to.equal(5); + expect(bid.native.ortb.ver).to.equal('1.2'); + expect(bid.native.ortb.assets[0].id).to.equal(1); + expect(bid.native.ortb.assets[0].img.url).to.equal('https://crcdn01.adnxs-simple.com/creative20/p/9325/2024/8/14/60018074/6ceb0f95-1465-4e90-b295-4b6e2aff3035.jpg'); + expect(bid.native.ortb.assets[0].img.w).to.equal(989); + expect(bid.native.ortb.assets[0].img.h).to.equal(742); + expect(bid.native.ortb.assets[1].id).to.equal(2); + expect(bid.native.ortb.assets[1].title.text).to.equal('This is a AST Native Creative'); + expect(bid.native.ortb.assets[2].id).to.equal(3); + expect(bid.native.ortb.assets[2].data.value).to.equal('AST'); + expect(bid.native.ortb.eventtrackers[0].event).to.equal(1); + expect(bid.native.ortb.eventtrackers[0].method).to.equal(1); + expect(bid.native.ortb.eventtrackers[0].url).to.contains(['https://nym2-ib.adnxs.com/it']); + }); + } + }); + + describe('getUserSyncs', function () { + it('should return an iframe sync if enabled and GDPR consent is given', function () { + const syncOptions = { + iframeEnabled: true + }; + const gdprConsent = { + gdprApplies: true, + consentString: '...', + vendorData: { + purpose: { + consents: { + 1: true + } + } + } + }; + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.deep.equal([{ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + }]); + }); + + it('should return a pixel sync if enabled', function () { + const syncOptions = { + pixelEnabled: true + }; + const syncs = spec.getUserSyncs(syncOptions, []); + expect(syncs).to.not.be.empty; + expect(syncs[0].type).to.equal('image'); + }); + }); +}); From 3f16865c5fe8b246163431da0a954b15aa02deb3 Mon Sep 17 00:00:00 2001 From: Hiroaki Kubota Date: Thu, 16 Oct 2025 16:37:12 +0900 Subject: [PATCH 014/147] Craft Bid Adapter : add user.eids to request (#13985) * [craftBidder] Add user.eids to request * [craftBidder] Add user.eids to request add test --------- Co-authored-by: Monis Qadri --- modules/craftBidAdapter.js | 11 +++++---- test/spec/modules/craftBidAdapter_spec.js | 27 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 3c1bea6cc89..cbb3d047e0b 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -25,16 +25,19 @@ export const spec = { buildRequests: function(bidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - const bidRequest = bidRequests[0]; + const bidRequest = bidRequests[0] || {}; const tags = bidRequests.map(bidToTag); - const schain = bidRequest?.ortb2?.source?.ext?.schain; + const schain = bidRequest.ortb2?.source?.ext?.schain; const payload = { tags: [...tags], ua: navigator.userAgent, sdk: { - version: '$prebid.version$' + version: '$prebid.version$', + }, + schain: schain, + user: { + eids: bidRequest.userIdAsEids, }, - schain: schain }; if (bidderRequest) { if (bidderRequest.gdprConsent) { diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index 92aa103093a..45922e1cc25 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -89,12 +89,28 @@ describe('craftAdapter', function () { bidderRequestId: '4a859978b5d4bd', auctionId: '8720f980-4639-4150-923a-e96da2f1de36', transactionId: 'e0c52da2-c008-491c-a910-c6765d948700', + ortb2: { + source: { + ext: { + schain: { + ver: '1.0', + complete: 1, + nodes: [], + }, + }, + }, + }, + userIdAsEids: [ + {source: 'foobar1.com', uids: [{id: 'xxxxxxx', atype: 1}]}, + {source: 'foobar2.com', uids: [{id: 'yyyyyyy', atype: 1}]}, + ], }]; const bidderRequest = { refererInfo: { topmostLocation: 'https://www.gacraft.jp/publish/craft-prebid-example.html' } }; + it('sends bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.method).to.equal('POST'); @@ -111,6 +127,17 @@ describe('craftAdapter', function () { expect(data.referrer_detection).to.deep.equals({ rd_ref: 'https://www.gacraft.jp/publish/craft-prebid-example.html' }); + expect(data.schain).to.deep.equals({ + complete: 1, + nodes: [], + ver: '1.0', + }); + expect(data.user).to.deep.equals({ + eids: [ + {source: 'foobar1.com', uids: [{id: 'xxxxxxx', atype: 1}]}, + {source: 'foobar2.com', uids: [{id: 'yyyyyyy', atype: 1}]}, + ] + }); }); }); From a361ae7c61757fbf68a4070cfdb8ca8c7d698cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C3=96zg=C3=BCn=20I=C5=9F=C4=B1kman?= Date: Thu, 16 Oct 2025 10:54:25 +0300 Subject: [PATCH 015/147] EmpowerBidAdapter: initial release (#13943) * EmpowerBidAdapter: initial release * EmpowerBidAdapter: initial release * some code changes after lint check * test bannerServerRequest changes from local url to server url * some fixes and improvements * unceswsary line removed --------- Co-authored-by: Monis Qadri --- modules/empowerBidAdapter.js | 262 +++++++ modules/empowerBidAdapter.md | 36 + test/spec/modules/empowerBidAdapter_spec.js | 798 ++++++++++++++++++++ 3 files changed, 1096 insertions(+) create mode 100644 modules/empowerBidAdapter.js create mode 100644 modules/empowerBidAdapter.md create mode 100644 test/spec/modules/empowerBidAdapter_spec.js diff --git a/modules/empowerBidAdapter.js b/modules/empowerBidAdapter.js new file mode 100644 index 00000000000..14e8be2a82d --- /dev/null +++ b/modules/empowerBidAdapter.js @@ -0,0 +1,262 @@ +import { + deepAccess, + mergeDeep, + logError, + replaceMacros, + triggerPixel, + deepSetValue, + isStr, + isArray, + getWinDimensions, +} from "../src/utils.js"; +import { registerBidder } from "../src/adapters/bidderFactory.js"; +import { config } from "../src/config.js"; +import { VIDEO, BANNER } from "../src/mediaTypes.js"; +import { getConnectionType } from "../libraries/connectionInfo/connectionUtils.js"; + +export const ENDPOINT = "https://bid.virgul.com/prebid"; + +const BIDDER_CODE = "empower"; +const GVLID = 1248; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: (bid) => + !!(bid && bid.params && bid.params.zone && bid.bidder === BIDDER_CODE), + + buildRequests: (bidRequests, bidderRequest) => { + const currencyObj = config.getConfig("currency"); + const currency = (currencyObj && currencyObj.adServerCurrency) || "USD"; + + const request = { + id: bidRequests[0].bidderRequestId, + at: 1, + imp: bidRequests.map((slot) => impression(slot, currency)), + site: { + page: bidderRequest.refererInfo.page, + domain: bidderRequest.refererInfo.domain, + ref: bidderRequest.refererInfo.ref, + publisher: { domain: bidderRequest.refererInfo.domain }, + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: + navigator.doNotTrack === "yes" || + navigator.doNotTrack === "1" || + navigator.msDoNotTrack === "1" + ? 1 + : 0, + h: screen.height, + w: screen.width, + language: navigator.language, + connectiontype: getConnectionType(), + }, + cur: [currency], + source: { + fd: 1, + tid: bidderRequest.ortb2?.source?.tid, + ext: { + prebid: "$prebid.version$", + }, + }, + user: {}, + regs: {}, + ext: {}, + }; + + if (bidderRequest.gdprConsent) { + request.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString || "", + }, + }; + request.regs = { + ext: { + gdpr: + bidderRequest.gdprConsent.gdprApplies !== undefined + ? bidderRequest.gdprConsent.gdprApplies + : true, + }, + }; + } + + if (bidderRequest.ortb2?.source?.ext?.schain) { + request.schain = bidderRequest.ortb2.source.ext.schain; + } + + let bidUserIdAsEids = deepAccess(bidRequests, "0.userIdAsEids"); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(request, "user.eids", bidUserIdAsEids); + } + + const commonFpd = bidderRequest.ortb2 || {}; + const { user, device, site, bcat, badv } = commonFpd; + if (site) { + mergeDeep(request, { site: site }); + } + if (user) { + mergeDeep(request, { user: user }); + } + if (badv) { + mergeDeep(request, { badv: badv }); + } + if (bcat) { + mergeDeep(request, { bcat: bcat }); + } + + if (user?.geo && device?.geo) { + request.device.geo = { ...request.device.geo, ...device.geo }; + request.user.geo = { ...request.user.geo, ...user.geo }; + } else { + if (user?.geo || device?.geo) { + request.user.geo = request.device.geo = user?.geo + ? { ...request.user.geo, ...user.geo } + : { ...request.user.geo, ...device.geo }; + } + } + + if (bidderRequest.ortb2?.device) { + mergeDeep(request.device, bidderRequest.ortb2.device); + } + + return { + method: "POST", + url: ENDPOINT, + data: JSON.stringify(request), + }; + }, + + interpretResponse: (bidResponse, bidRequest) => { + const idToImpMap = {}; + const idToBidMap = {}; + + if (!bidResponse["body"]) { + return []; + } + if (!bidRequest.data) { + return []; + } + const requestImps = parse(bidRequest.data); + if (!requestImps) { + return []; + } + requestImps.imp.forEach((imp) => { + idToImpMap[imp.id] = imp; + }); + bidResponse = bidResponse.body; + if (bidResponse) { + bidResponse.seatbid.forEach((seatBid) => + seatBid.bid.forEach((bid) => { + idToBidMap[bid.impid] = bid; + }) + ); + } + const bids = []; + Object.keys(idToImpMap).forEach((id) => { + const imp = idToImpMap[id]; + const result = idToBidMap[id]; + + if (result) { + const bid = { + requestId: id, + cpm: result.price, + creativeId: result.crid, + ttl: 300, + netRevenue: true, + mediaType: imp.video ? VIDEO : BANNER, + currency: bidResponse.cur, + }; + if (imp.video) { + bid.vastXml = result.adm; + } else if (imp.banner) { + bid.ad = result.adm; + } + bid.width = result.w; + bid.height = result.h; + if (result.burl) bid.burl = result.burl; + if (result.nurl) bid.nurl = result.nurl; + if (result.adomain) { + bid.meta = { + advertiserDomains: result.adomain, + }; + } + bids.push(bid); + } + }); + return bids; + }, + + onBidWon: (bid) => { + if (bid.nurl && isStr(bid.nurl)) { + bid.nurl = replaceMacros(bid.nurl, { + AUCTION_PRICE: bid.cpm, + AUCTION_CURRENCY: bid.cur, + }); + triggerPixel(bid.nurl); + } + }, +}; + +function impression(slot, currency) { + let bidFloorFromModule; + if (typeof slot.getFloor === "function") { + const floorInfo = slot.getFloor({ + currency: "USD", + mediaType: "*", + size: "*", + }); + bidFloorFromModule = + floorInfo?.currency === "USD" ? floorInfo?.floor : undefined; + } + const imp = { + id: slot.bidId, + bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, + bidfloorcur: + (bidFloorFromModule && "USD") || + slot.params.bidfloorcur || + currency || + "USD", + tagid: "" + (slot.params.zone || ""), + }; + + if (slot.mediaTypes.banner) { + imp.banner = bannerImpression(slot); + } else if (slot.mediaTypes.video) { + imp.video = deepAccess(slot, "mediaTypes.video"); + } + imp.ext = slot.params || {}; + const { innerWidth, innerHeight } = getWinDimensions(); + imp.ext.ww = innerWidth || ""; + imp.ext.wh = innerHeight || ""; + return imp; +} + +function bannerImpression(slot) { + const sizes = slot.mediaTypes.banner.sizes || slot.sizes; + return { + format: sizes.map((s) => ({ w: s[0], h: s[1] })), + w: sizes[0][0], + h: sizes[0][1], + }; +} + +function parse(rawResponse) { + try { + if (rawResponse) { + if (typeof rawResponse === "object") { + return rawResponse; + } else { + return JSON.parse(rawResponse); + } + } + } catch (ex) { + logError("empowerBidAdapter", "ERROR", ex); + } + return null; +} + +registerBidder(spec); diff --git a/modules/empowerBidAdapter.md b/modules/empowerBidAdapter.md new file mode 100644 index 00000000000..b627cd25282 --- /dev/null +++ b/modules/empowerBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +Module Name: Empower Bid Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid@empower.net + +# Description + +Module that connects to Empower's demand sources + +This adapter requires setup and approval from Empower.net. +Please reach out to your account team or info@empower.net for more information. + +# Test Parameters +```javascript + var adUnits = [ + { + code: '/19968336/prebid_banner_example_1', + mediaTypes: { + banner: { + sizes: [[970, 250], [300, 250]], + } + }, + bids: [{ + bidder: 'empower', + params: { + bidfloor: 0.50, + zone: 123456, + site: 'example' + }, + }] + } + ]; +``` diff --git a/test/spec/modules/empowerBidAdapter_spec.js b/test/spec/modules/empowerBidAdapter_spec.js new file mode 100644 index 00000000000..b260d112499 --- /dev/null +++ b/test/spec/modules/empowerBidAdapter_spec.js @@ -0,0 +1,798 @@ +import { expect } from "chai"; +import { spec, ENDPOINT } from "modules/empowerBidAdapter.js"; +import { config } from "src/config.js"; +import { setConfig as setCurrencyConfig } from "../../../modules/currency.js"; +import * as utils from "src/utils.js"; + +describe("EmpowerAdapter", function () { + let baseBidRequest; + + let bannerBidRequest; + let bannerServerResponse; + let bannerServerRequest; + + let videoBidRequest; + let videoServerResponse; + let videoServerRequest; + + let bidderRequest; + + beforeEach(function () { + bidderRequest = { + refererInfo: { + page: "https://publisher.com/home", + domain: "publisher.com", + }, + }; + + baseBidRequest = { + bidder: "empower", + params: { + zone: "123456", + }, + bidId: "2ffb201a808da7", + bidderRequestId: "678e3fbad375ce", + auctionId: "c45dd708-a418-42ec-b8a7-b70a6c6fab0a", + transactionId: "d45dd707-a418-42ec-b8a7-b70a6c6fab0b", + }; + + bannerBidRequest = { + ...baseBidRequest, + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [300, 250], + ], + }, + }, + sizes: [ + [640, 320], + [300, 600], + ], + }; + + bannerServerResponse = { + id: "678e3fbad375ce", + cur: "USD", + seatbid: [ + { + bid: [ + { + id: "288f5e3e-f122-4928-b5df-434f5b664788", + impid: "2ffb201a808da7", + price: 0.12, + cid: "12", + crid: "123", + adomain: ["empower.net"], + adm: '', + burl: "https://localhost:8081/url/b?d=b604923d-f420-4227-a8af-09b332b33c2d&c=USD&p=${AUCTION_PRICE}&bad=33d141da-dd49-45fc-b29d-1ed38a2168df&gc=0", + nurl: "https://ng.virgul.com/i_wu?a=fac123456&ext=,ap0.12,acUSD,sp0.1,scUSD", + w: 640, + h: 360, + }, + ], + }, + ], + }; + + bannerServerRequest = { + method: "POST", + url: "https://bid.virgul.com/prebid", + data: JSON.stringify({ + id: "678e3fbad375ce", + imp: [ + { + id: "2ffb201a808da7", + bidfloor: 5, + bidfloorcur: "USD", + tagid: "123456", + banner: { + w: 640, + h: 360, + format: [ + { w: 640, h: 360 }, + { w: 320, h: 320 }, + ], + }, + }, + ], + site: { + publisher: { + id: "44bd6161-667e-4a68-8fa4-18b5ae2d8c89", + }, + id: "1d973061-fe5d-4622-a071-d8a01d72ba7d", + ref: "", + page: "http://localhost", + domain: "localhost", + }, + app: null, + device: { + ua: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/61.0.3163.100 Safari/537.36", + language: "en-US", + }, + isPrebid: true, + }), + }; + + videoBidRequest = { + ...baseBidRequest, + mediaTypes: { video: { playerSize: [[640, 360]] } }, + }; + + videoServerResponse = { + id: "678e3fbad375ce", + cur: "USD", + seatbid: [ + { + bid: [ + { + id: "288f5e3e-f122-4928-b5df-434f5b664788", + impid: "2ffb201a808da7", + price: 0.12, + cid: "12", + crid: "123", + adomain: ["empower.net"], + adm: "", + burl: "https://localhost:8081/url/b?d=b604923d-f420-4227-a8af-09b332b33c2d&c=USD&p=${AUCTION_PRICE}&bad=33d141da-dd49-45fc-b29d-1ed38a2168df&gc=0", + nurl: "https://ng.virgul.com/i_wu?a=fac123456&ext=,ap01.12,acUSD,sp0.1,scUSD", + w: 640, + h: 360, + }, + ], + }, + ], + }; + + videoServerRequest = { + method: "POST", + url: "https://bid.virgul.com/prebid", + data: JSON.stringify({ + id: "678e3fbad375ce", + imp: [ + { + id: "2ffb201a808da7", + bidfloor: 5, + bidfloorcur: "USD", + tagid: "123456", + video: { playerSize: [[640, 360]] }, + }, + ], + site: { + publisher: { + id: "44bd6161-667e-4a68-8fa4-18b5ae2d8c89", + }, + id: "1d973061-fe5d-4622-a071-d8a01d72ba7d", + ref: "", + page: "http://localhost", + domain: "localhost", + }, + app: null, + device: { + ua: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/61.0.3163.100 Safari/537.36", + language: "en-US", + }, + isPrebid: true, + }), + }; + }); + + describe("Banner", function () { + describe("spec.isBidRequestValid", function () { + it("should return true when the required params are passed to banner", function () { + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(true); + }); + + it('should return false when the "zone" param is missing for banner', function () { + bannerBidRequest.params = { + bidfloor: 5.0, + }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(false); + }); + + it("should return false when no bid params are passed to banner", function () { + bannerBidRequest.params = {}; + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(false); + }); + }); + + describe("spec.buildRequests", function () { + it("should create a POST request for every bid", function () { + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + expect(request.method).to.equal("POST"); + expect(request.url).to.equal(ENDPOINT); + }); + + it("should attach request data to banner", function () { + config.setConfig({ + currency: { + adServerCurrency: "EUR", + }, + }); + + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal("$prebid.version$"); + expect(data.id).to.equal(bannerBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(0); + expect(data.imp[0].bidfloorcur).to.equal("EUR"); + expect(data.imp[0].tagid).to.equal("123456"); + expect(data.imp[0].ext.zone).to.equal(bannerBidRequest.params.zone); + expect(data.site.page).to.equal(bidderRequest.refererInfo.page); + expect(data.site.domain).to.equal(bidderRequest.refererInfo.domain); + expect(data.device).to.deep.contain({ + ua: navigator.userAgent, + language: navigator.language, + }); + expect(data.cur).to.deep.equal(["EUR"]); + }); + + describe("spec.interpretResponse", function () { + it("should return no bids if the response is invalid request", function () { + const bidResponse = spec.interpretResponse( + { body: bannerServerResponse }, + {} + ); + expect(bidResponse.length).to.equal(0); + }); + + it("should return no bids if the response is invalid body json", function () { + const bidResponse = spec.interpretResponse( + { body: bannerServerResponse }, + { data: "invalid body " } + ); + expect(bidResponse.length).to.equal(0); + }); + + it("should return a valid bid a valid body", function () { + bannerServerRequest.data = JSON.parse(bannerServerRequest.data); + const bidResponse = spec.interpretResponse( + { body: bannerServerResponse }, + bannerServerRequest + ); + expect(bidResponse.length).to.equal(1); + }); + + it("should return no bids if the response is not valid to banner", function () { + const bidResponse = spec.interpretResponse( + { body: null }, + bannerServerRequest + ); + expect(bidResponse.length).to.equal(0); + }); + + it("should return a valid bid response to banner", function () { + const bidResponse = spec.interpretResponse( + { body: bannerServerResponse }, + bannerServerRequest + )[0]; + + expect(bidResponse).to.contain({ + requestId: bannerBidRequest.bidId, + cpm: bannerServerResponse.seatbid[0].bid[0].price, + creativeId: bannerServerResponse.seatbid[0].bid[0].crid, + ttl: 300, + netRevenue: true, + mediaType: "banner", + currency: bannerServerResponse.cur, + ad: bannerServerResponse.seatbid[0].bid[0].adm, + width: bannerServerResponse.seatbid[0].bid[0].w, + height: bannerServerResponse.seatbid[0].bid[0].h, + burl: bannerServerResponse.seatbid[0].bid[0].burl, + nurl: bannerServerResponse.seatbid[0].bid[0].nurl, + }); + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ["empower.net"], + }); + }); + }); + }); + + describe("Video", function () { + describe("spec.isBidRequestValid", function () { + it("should return true when the required params are passed", function () { + expect(spec.isBidRequestValid(videoBidRequest)).to.equal(true); + }); + + it('should return false when the "zone" param is missing', function () { + videoBidRequest.params = { + bidfloor: 5.0, + }; + expect(spec.isBidRequestValid(videoBidRequest)).to.equal(false); + }); + + it("should return false when no bid params are passed", function () { + videoBidRequest.params = {}; + expect(spec.isBidRequestValid(videoBidRequest)).to.equal(false); + }); + }); + + describe("spec.buildRequests", function () { + it("should create a POST request for every bid", function () { + const request = spec.buildRequests([videoBidRequest], bidderRequest); + expect(request.method).to.equal("POST"); + expect(request.url).to.equal(ENDPOINT); + }); + + it("should attach request data to video", function () { + config.setConfig({ + currency: { + adServerCurrency: "EUR", + }, + }); + + const request = spec.buildRequests([videoBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal("$prebid.version$"); + expect(data.id).to.equal(videoBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(0); + expect(data.imp[0].bidfloorcur).to.equal("EUR"); + expect(data.imp[0].tagid).to.equal("123456"); + + expect(data.imp[0].ext.zone).to.equal(videoBidRequest.params.zone); + expect(data.site.page).to.equal(bidderRequest.refererInfo.page); + expect(data.site.domain).to.equal(bidderRequest.refererInfo.domain); + expect(data.device).to.deep.contain({ + ua: navigator.userAgent, + language: navigator.language, + }); + expect(data.cur).to.deep.equal(["EUR"]); + }); + + it("should get bid floor from module", function () { + const floorModuleData = { + currency: "USD", + floor: 3.2, + }; + videoBidRequest.getFloor = function () { + return floorModuleData; + }; + const request = spec.buildRequests([videoBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal("$prebid.version$"); + expect(data.id).to.equal(videoBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); + }); + + it("should send gdpr data when gdpr does not apply", function () { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + }, + }; + const request = spec.buildRequests([videoBidRequest], { + ...bidderRequest, + ...gdprData, + }); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: "", + }, + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false, + }, + }); + }); + + it("should send gdpr data when gdpr applies", function () { + const tcString = "sometcstring"; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString, + }, + }; + const request = spec.buildRequests([videoBidRequest], { + ...bidderRequest, + ...gdprData, + }); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: tcString, + }, + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true, + }, + }); + }); + }); + + describe("spec.interpretResponse", function () { + it("should return no bids if the response is not valid", function () { + const bidResponse = spec.interpretResponse( + { body: null }, + videoServerRequest + ); + expect(bidResponse.length).to.equal(0); + }); + + it("should return a valid bid response to video", function () { + const bidResponse = spec.interpretResponse( + { body: videoServerResponse }, + videoServerRequest + )[0]; + + expect(bidResponse).to.contain({ + requestId: videoBidRequest.bidId, + cpm: videoServerResponse.seatbid[0].bid[0].price, + creativeId: videoServerResponse.seatbid[0].bid[0].crid, + ttl: 300, + netRevenue: true, + mediaType: "video", + currency: videoServerResponse.cur, + vastXml: videoServerResponse.seatbid[0].bid[0].adm, + }); + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ["empower.net"], + }); + }); + }); + }); + + describe("Modules", function () { + it("should attach user Ids", function () { + const userIdAsEids = { + userIdAsEids: [ + { + source: "pubcid.org", + uids: [ + { + id: "abcxyzt", + atype: 1, + }, + ], + }, + { + source: "criteo.com", + uids: [ + { + id: "qwertyu", + atype: 1, + }, + ], + }, + ], + }; + bannerBidRequest = { ...bannerBidRequest, ...userIdAsEids }; + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + const data = JSON.parse(request.data); + + expect(data.user.eids.length).to.equal(2); + expect(data.user.eids[0].source).to.equal("pubcid.org"); + expect(data.user.eids[1].uids.length).to.equal(1); + expect(data.user.eids[1].uids[0].id).to.equal("qwertyu"); + }); + + it("should get bid floor from module", function () { + const floorModuleData = { + currency: "USD", + floor: 3.2, + }; + bannerBidRequest.getFloor = function () { + return floorModuleData; + }; + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal("$prebid.version$"); + expect(data.id).to.equal(bannerBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); + }); + + it("should send gdpr data when gdpr does not apply", function () { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...gdprData, + }); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: "", + }, + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false, + }, + }); + }); + + it("should send gdpr data when gdpr applies", function () { + const tcString = "sometcstring"; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...gdprData, + }); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: tcString, + }, + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true, + }, + }); + }); + }); + describe("Ortb2", function () { + it("should attach schain", function () { + const schain = { + ortb2: { + source: { + ext: { + schain: { + ver: "1.0", + complete: 1, + nodes: [ + { + asi: "empower.net", + sid: "111222333", + hp: 1, + }, + ], + }, + }, + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...schain, + }); + const data = JSON.parse(request.data); + expect(data.schain.ver).to.equal("1.0"); + expect(data.schain.nodes.length).to.equal(1); + expect(data.schain.nodes[0].sid).to.equal("111222333"); + expect(data.schain.nodes[0].asi).to.equal("empower.net"); + }); + + it("should attach badv", function () { + const badv = { + ortb2: { badv: ["bad.example.com"] }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...badv, + }); + const data = JSON.parse(request.data); + expect(data.badv.length).to.equal(1); + expect(data.badv[0]).to.equal("bad.example.com"); + }); + + it("should attach bcat", function () { + const bcat = { + ortb2: { bcat: ["IAB-1-2", "IAB-1-2"] }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...bcat, + }); + const data = JSON.parse(request.data); + expect(data.bcat.length).to.equal(2); + expect(data.bcat).to.deep.equal(bcat.ortb2.bcat); + }); + + it("should override initial device", function () { + const device = { + ortb2: { + device: { + w: 390, + h: 844, + dnt: 0, + ua: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", + language: "en", + ext: { + vpw: 390, + vph: 844, + }, + sua: { + source: 1, + browsers: [], + mobile: 1, + }, + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...device, + }); + const data = JSON.parse(request.data); + expect(data.device.ua).to.equal(device.ortb2.device.ua); + expect(data.device.sua.mobile).to.equal(device.ortb2.device.sua.mobile); + }); + + it("should override initial site", function () { + const site = { + ortb2: { + site: { + publisher: { + domain: "empower.net", + }, + page: "https://empower.net/prebid", + name: "empower.net", + cat: [], + sectioncat: [], + pagecat: [], + ref: "", + ext: { + data: {}, + }, + content: { + language: "en", + }, + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...site, + }); + const data = JSON.parse(request.data); + expect(data.site.page).to.equal(site.ortb2.site.page); + expect(data.site.domain).to.equal("publisher.com"); + }); + + it("should attach device and user geo via device", function () { + const device = { + ortb2: { + device: { + geo: { + lat: 1, + lon: -1, + }, + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...device, + }); + const data = JSON.parse(request.data); + expect(data.device.geo.lat).to.equal(device.ortb2.device.geo.lat); + expect(data.user.geo.lat).to.equal(device.ortb2.device.geo.lat); + }); + + it("should attach device and user geo via user", function () { + const ortb2 = { + ortb2: { + user: { + geo: { + lat: 1, + lon: -1, + }, + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...ortb2, + }); + const data = JSON.parse(request.data); + expect(data.device.geo.lat).to.equal(ortb2.ortb2.user.geo.lat); + expect(data.user.geo.lat).to.equal(ortb2.ortb2.user.geo.lat); + }); + + it("should attach device and user geo both device/user", function () { + const ortb2 = { + ortb2: { + user: { + geo: { + lat: 1, + lon: -1, + }, + }, + device: { + geo: { + lat: 2, + lon: -1, + }, + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...ortb2, + }); + const data = JSON.parse(request.data); + expect(data.device.geo.lat).to.equal(ortb2.ortb2.device.geo.lat); + expect(data.user.geo.lat).to.equal(ortb2.ortb2.user.geo.lat); + }); + + it("should override initial user", function () { + const user = { + ortb2: { + user: { + gender: "F", + }, + }, + }; + const request = spec.buildRequests([bannerBidRequest], { + ...bidderRequest, + ...user, + }); + const data = JSON.parse(request.data); + expect(data.user.gender).to.equal(user.ortb2.user.gender); + }); + }); + }); + + describe("onBidWon", function () { + beforeEach(function () { + sinon.stub(utils, "triggerPixel"); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it("Should not trigger pixel if bid does not contain nurl", function () { + spec.onBidWon({}); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it("Should not trigger pixel if nurl is empty", function () { + spec.onBidWon({ + nurl: "", + }); + + expect(utils.triggerPixel.called).to.be.false; + }); + + it("Should trigger pixel with replaced nurl if nurl is not empty", function () { + const bidResponse = spec.interpretResponse( + { body: bannerServerResponse }, + bannerServerRequest + ); + const bidToWon = bidResponse[0]; + bidToWon.adserverTargeting = { + hb_pb: 0.1, + }; + spec.onBidWon(bidToWon); + + expect(utils.triggerPixel.callCount).to.be.equal(1); + expect(utils.triggerPixel.firstCall.args[0]).to.be.equal( + "https://ng.virgul.com/i_wu?a=fac123456&ext=,ap0.12,acUSD,sp0.1,scUSD" + ); + setCurrencyConfig({}); + }); + }); +}); From 38862efa54fd975ce67d075efb42739fd118cf3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:59:39 -0400 Subject: [PATCH 016/147] Bump @babel/runtime from 7.28.3 to 7.28.4 (#14008) Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.28.3 to 7.28.4. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.28.4/packages/babel-runtime) --- updated-dependencies: - dependency-name: "@babel/runtime" dependency-version: 7.28.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 13f81883af8..3b331152a46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.28.3", "@babel/preset-typescript": "^7.26.0", - "@babel/runtime": "^7.28.3", + "@babel/runtime": "^7.28.4", "core-js": "^3.45.1", "crypto-js": "^4.2.0", "dlv": "^1.1.3", @@ -1626,9 +1626,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" diff --git a/package.json b/package.json index 433a9cad791..355521b47b6 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.28.3", "@babel/preset-typescript": "^7.26.0", - "@babel/runtime": "^7.28.3", + "@babel/runtime": "^7.28.4", "core-js": "^3.45.1", "crypto-js": "^4.2.0", "dlv": "^1.1.3", From f9e9f78adf064b5b49564ee9b9af33cd2a2c4662 Mon Sep 17 00:00:00 2001 From: Nelson-optable Date: Thu, 16 Oct 2025 10:36:44 -0400 Subject: [PATCH 017/147] optableRtdProvider: update documentation (#14017) * Update optableRtdProvider.md * Update optableRtdProvider.md --- modules/optableRtdProvider.md | 47 ++++------------------------------- 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/modules/optableRtdProvider.md b/modules/optableRtdProvider.md index 1250437f6f0..45fc7d589d7 100644 --- a/modules/optableRtdProvider.md +++ b/modules/optableRtdProvider.md @@ -6,6 +6,10 @@ Module Type: RTD Provider Maintainer: prebid@optable.co +## Minimal Prebid.js Versions + +Prebid.js minimum version: 9.53.2+, or 10.2+ + ## Description Optable RTD submodule enriches the OpenRTB request by populating `user.ext.eids` and `user.data` using an identity graph and audience segmentation service hosted by Optable on behalf of the publisher. This RTD submodule primarily relies on the Optable bundle loaded on the page, which leverages the Optable-specific Visitor ID and other PPIDs to interact with the identity graph, enriching the bid request with additional user IDs and audience data. @@ -30,23 +34,18 @@ In order to use the module you first need to register with Optable and obtain a ``` -In this case bundleUrl parameter is not needed and the script will await bundle loading before delegating to it. - ### Configuration -This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to 1000 ms and make sure `waitForIt` is set to `true` for the `Optable` RTD provider. +This module is configured as part of the `realTimeData.dataProviders`. ```javascript pbjs.setConfig({ debug: true, // we recommend turning this on for testing as it adds more logging realTimeData: { - auctionDelay: 1000, dataProviders: [ { name: 'optable', - waitForIt: true, // should be true, otherwise the auctionDelay will be ignored params: { - bundleUrl: '', adserverTargeting: '', }, }, @@ -55,48 +54,12 @@ pbjs.setConfig({ }); ``` -### Additional input to the module - -Optable bundle may use PPIDs (publisher provided IDs) from the `user.ext.eids` as input. - -In addition, other arbitrary keys can be used as input, f.e. the following: - -- `optableRtdConfig.email` - a SHA256-hashed user email -- `optableRtdConfig.phone` - a SHA256-hashed [E.164 normalized phone](https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization) (meaning a phone number consisting of digits and leading plus sign without spaces or any additional characters, f.e. a US number would be: `+12345678999`) -- `optableRtdConfig.postal_code` - a ZIP postal code string - -Each of these identifiers is completely optional and can be provided through `pbjs.mergeConfig(...)` like so: - -```javascript -pbjs.mergeConfig({ - optableRtdConfig: { - email: await sha256("test@example.com"), - phone: await sha256("12345678999"), - postal_code: "61054" - } -}) -``` - -Where `sha256` function can be defined as: - -```javascript -async function sha256(input) { - return [...new Uint8Array( - await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input)) - )].map(b => b.toString(16).padStart(2, "0")).join(""); -} -``` - -To handle PPIDs and the above input - a custom `handleRtd` function may need to be provided. - ### Parameters | Name | Type | Description | Default | Notes | |--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|----------| | name | String | Real time data module name | Always `optable` | | -| waitForIt | Boolean | Should be set `true` together with `auctionDelay: 1000` | `false` | | | params | Object | | | | -| params.bundleUrl | String | Optable bundle URL | `null` | Optional | | params.adserverTargeting | Boolean | If set to `true`, targeting keywords will be passed to the ad server upon auction completion | `true` | Optional | | params.handleRtd | Function | An optional function that uses Optable data to enrich `reqBidsConfigObj` with the real-time data. If not provided, the module will do a default call to Optable bundle. The function signature is `[async] (reqBidsConfigObj, optableExtraData, mergeFn) => {}` | `null` | Optional | From 84247e08f7ea7265f7561df0971d096e8667f07f Mon Sep 17 00:00:00 2001 From: LiveSurendra Date: Thu, 16 Oct 2025 20:41:38 +0530 Subject: [PATCH 018/147] AtsAnalyticsAdapter: get the user Ids from userIdAsEids (#14022) * [ATS-68316] Update to get userId as per prebid version 10+ * [ATS-68316] Add unit test case for the fix --------- Co-authored-by: Sunath --- modules/atsAnalyticsAdapter.js | 21 +- test/spec/modules/atsAnalyticsAdapter_spec.js | 214 +++++++++++++++++- 2 files changed, 229 insertions(+), 6 deletions(-) diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 4ab98a77717..dcb43eb5f22 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -209,14 +209,31 @@ const browsersList = [ const listOfSupportedBrowsers = ['Safari', 'Chrome', 'Firefox', 'Microsoft Edge']; -function bidRequestedHandler(args) { +export function bidRequestedHandler(args) { const envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); const envelopeSource = envelopeSourceCookieValue === 'true'; let requests; requests = args.bids.map(function(bid) { return { envelope_source: envelopeSource, - has_envelope: bid.userId ? !!bid.userId.idl_env : false, + has_envelope: (function() { + // Check userIdAsEids for Prebid v10.0+ compatibility + if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { + const liverampEid = bid.userIdAsEids.find(eid => + eid.source === 'liveramp.com' + ); + if (liverampEid && liverampEid.uids && liverampEid.uids.length > 0) { + return true; + } + } + + // Fallback for older versions (backward compatibility) + if (bid.userId && bid.userId.idl_env) { + return true; + } + + return false; + })(), bidder: bid.bidder, bid_id: bid.bidId, auction_id: args.auctionId, diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index 4b8b908a792..c294a5e08d0 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import atsAnalyticsAdapter, {parseBrowser, analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; +import atsAnalyticsAdapter, {parseBrowser, analyticsUrl, bidRequestedHandler} from '../../../modules/atsAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import {server} from '../../mocks/xhr.js'; @@ -178,9 +178,12 @@ describe('ats analytics adapter', function () { // Step 6: Send bid won event events.emit(EVENTS.BID_WON, wonRequest); - sandbox.stub(getGlobal(), 'getAllWinningBids').callsFake((key) => { - return [wonRequest] - }); + // Stub getAllWinningBids before auction end processing + const globalObj = getGlobal(); + if (typeof globalObj.getAllWinningBids !== 'function') { + globalObj.getAllWinningBids = function() { return []; }; + } + sandbox.stub(globalObj, 'getAllWinningBids').returns([wonRequest]); clock.tick(2000); @@ -274,5 +277,208 @@ describe('ats analytics adapter', function () { }); expect(utils.logError.called).to.equal(true); }) + + describe('has_envelope logic for Prebid v10.0+ compatibility', function () { + beforeEach(function () { + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + sinon.stub(Math, 'random').returns(0.99); + storage.setCookie('_lr_env_src_ats', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); + + // Enable analytics for testing + atsAnalyticsAdapter.enableAnalytics({ + options: { pid: '10433394' } + }); + }); + + it('should return true when userIdAsEids contains liveramp.com source with valid uids', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [{ + 'source': 'liveramp.com', + 'uids': [{'id': 'AmThEbO1ssIWjrNdU4noT4ZFBILSVBBYHbipOYt_JP40e5nZdXns2g'}] + }] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(true); + }); + + it('should return false when userIdAsEids contains liveramp source but no uids', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [{ + 'source': 'liveramp.com', + 'uids': [] + }] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should return false when userIdAsEids contains non-liveramp sources', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [{ + 'source': 'id5-sync.com', + 'uids': [{'id': 'some-other-id'}] + }] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should return false when userIdAsEids is empty array', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should return false when userIdAsEids is not present', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17' + // No userIdAsEids property + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should return false when userIdAsEids is not an array', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': 'not-an-array' + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should return true for legacy userId.idl_env (backward compatibility)', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userId': { + 'idl_env': 'AmThEbO1ssIWjrNdU4noT4ZFBILSVBBYHbipOYt_JP40e5nZdXns2g' + } + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(true); + }); + + it('should return false when userIdAsEids has liveramp source but uids is null', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [{ + 'source': 'liveramp.com', + 'uids': null + }] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should return false when userIdAsEids has liveramp source but no uids property', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [{ + 'source': 'liveramp.com' + // No uids property + }] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(false); + }); + + it('should handle multiple userIdAsEids entries and find liveramp source', function () { + const bidRequestArgs = { + 'bidderCode': 'appnexus', + 'auctionStart': 1580739265161, + 'bids': [{ + 'bidder': 'appnexus', + 'bidId': '30c77d079cdf17', + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [{'id': 'id5-value'}] + }, + { + 'source': 'liveramp.com', + 'uids': [{'id': 'liveramp-value'}] + }, + { + 'source': 'criteo.com', + 'uids': [{'id': 'criteo-value'}] + } + ] + }], + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + }; + + const result = bidRequestedHandler(bidRequestArgs); + expect(result[0].has_envelope).to.equal(true); + }); + }) }) }) From 471a2fe1deb8d4d5468413e98b492d6de14f66a4 Mon Sep 17 00:00:00 2001 From: Invis Date: Thu, 16 Oct 2025 18:34:51 +0200 Subject: [PATCH 019/147] SmartyTech Bid Adapter: Add userId and consent data support with chunking capability (#13945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SmartyTech Bid Adapter: Add userId, consent data support and chunking capability - Add userIdAsEids transmission when available - Add GDPR, CCPA/USP, and COPPA consent support - Add configurable chunking of bid requests to control number of ads per request - Maintain backward compatibility - Add comprehensive test coverage for all new features Results of gulp lint: ✅ No issues Results of gulp test: ✅ All tests pass * Smartytech bid adapter. Avoid send undefined for gdprApplies and guarantee for config get --- modules/smartytechBidAdapter.js | 54 +++++- .../spec/modules/smartytechBidAdapter_spec.js | 175 ++++++++++++++++-- 2 files changed, 206 insertions(+), 23 deletions(-) diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 6f3de10be3c..c461a5707ab 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -1,6 +1,8 @@ -import {buildUrl, deepAccess} from '../src/utils.js' +import {buildUrl, deepAccess, isArray} from '../src/utils.js' import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'smartytech'; export const ENDPOINT_PROTOCOL = 'https'; @@ -80,20 +82,60 @@ export const spec = { } } + // Add user IDs if available + const userIds = deepAccess(validBidRequest, 'userIdAsEids'); + if (userIds && isArray(userIds) && userIds.length > 0) { + oneRequest.userIds = userIds; + } + + // Add GDPR consent if available + if (bidderRequest && bidderRequest.gdprConsent) { + oneRequest.gdprConsent = { + consentString: bidderRequest.gdprConsent.consentString || '' + }; + + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + oneRequest.gdprConsent.gdprApplies = bidderRequest.gdprConsent.gdprApplies; + } + + if (bidderRequest.gdprConsent.addtlConsent) { + oneRequest.gdprConsent.addtlConsent = bidderRequest.gdprConsent.addtlConsent; + } + } + + // Add CCPA/USP consent if available + if (bidderRequest && bidderRequest.uspConsent) { + oneRequest.uspConsent = bidderRequest.uspConsent; + } + + // Add COPPA flag if configured + const coppa = config.getConfig('coppa'); + if (coppa) { + oneRequest.coppa = coppa; + } + return oneRequest }); - const adPartnerRequestUrl = buildUrl({ + const smartytechRequestUrl = buildUrl({ protocol: ENDPOINT_PROTOCOL, hostname: ENDPOINT_DOMAIN, pathname: ENDPOINT_PATH, }); - return { + // Get chunk size from adapter configuration + const adapterSettings = config.getConfig(BIDDER_CODE) || {}; + const chunkSize = deepAccess(adapterSettings, 'chunkSize', 10); + + // Split bid requests into chunks + const bidChunks = chunk(bidRequests, chunkSize); + + // Return array of request objects, one for each chunk + return bidChunks.map(bidChunk => ({ method: 'POST', - url: adPartnerRequestUrl, - data: bidRequests - }; + url: smartytechRequestUrl, + data: bidChunk + })); }, interpretResponse: function (serverResponse, bidRequest) { diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index fe6af7ff101..24383e3e836 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -182,6 +182,43 @@ function mockRefererData() { } } +function mockBidderRequestWithConsents() { + return { + refererInfo: { + page: 'https://some-test.page' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA', + addtlConsent: '1~1.35.41.101' + }, + uspConsent: '1YNN' + } +} + +function mockBidRequestWithUserIds(mediaType, size, customSizes) { + const requests = mockBidRequestListData(mediaType, size, customSizes); + return requests.map(request => ({ + ...request, + userIdAsEids: [ + { + source: 'unifiedid.com', + uids: [{ + id: 'test-unified-id', + atype: 1 + }] + }, + { + source: 'pubcid.org', + uids: [{ + id: 'test-pubcid', + atype: 1 + }] + } + ] + })); +} + function mockResponseData(requestData) { const data = {} requestData.data.forEach((request, index) => { @@ -229,20 +266,25 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { }); it('correct request URL', () => { const request = spec.buildRequests(mockBidRequest, mockReferer); - expect(request.url).to.be.equal(`${ENDPOINT_PROTOCOL}://${ENDPOINT_DOMAIN}${ENDPOINT_PATH}`) + request.forEach(req => { + expect(req.url).to.be.equal(`${ENDPOINT_PROTOCOL}://${ENDPOINT_DOMAIN}${ENDPOINT_PATH}`) + }); }); it('correct request method', () => { const request = spec.buildRequests(mockBidRequest, mockReferer); - expect(request.method).to.be.equal(`POST`) + request.forEach(req => { + expect(req.method).to.be.equal(`POST`) + }); }); it('correct request data', () => { - const data = spec.buildRequests(mockBidRequest, mockReferer).data; - data.forEach((request, index) => { - expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); - expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); - expect(request.bidId).to.be.equal(mockBidRequest[index].bidId); - expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); - expect(request.referer).to.be.equal(mockReferer.refererInfo.page); + const request = spec.buildRequests(mockBidRequest, mockReferer); + const data = request.flatMap(resp => resp.data); + data.forEach((req, index) => { + expect(req.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); + expect(req.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); + expect(req.bidId).to.be.equal(mockBidRequest[index].bidId); + expect(req.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); + expect(req.referer).to.be.equal(mockReferer.refererInfo.page); }) }); }); @@ -256,9 +298,10 @@ describe('SmartyTechDSPAdapter: buildRequests banner custom size', () => { }); it('correct request data', () => { - const data = spec.buildRequests(mockBidRequest, mockReferer).data; - data.forEach((request, index) => { - expect(request.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes); + const request = spec.buildRequests(mockBidRequest, mockReferer); + const data = request.flatMap(resp => resp.data); + data.forEach((req, index) => { + expect(req.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes); }) }); }); @@ -272,9 +315,10 @@ describe('SmartyTechDSPAdapter: buildRequests video custom size', () => { }); it('correct request data', () => { - const data = spec.buildRequests(mockBidRequest, mockReferer).data; - data.forEach((request, index) => { - expect(request.video.sizes).to.be.equal(mockBidRequest[index].params.sizes); + const request = spec.buildRequests(mockBidRequest, mockReferer); + const data = request.flatMap(resp => resp.data); + data.forEach((req, index) => { + expect(req.video.sizes).to.be.equal(mockBidRequest[index].params.sizes); }) }); }); @@ -287,7 +331,7 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { beforeEach(() => { const brData = mockBidRequestListData('banner', 2, []); mockReferer = mockRefererData(); - request = spec.buildRequests(brData, mockReferer); + request = spec.buildRequests(brData, mockReferer)[0]; mockBidRequest = { data: brData } @@ -333,7 +377,7 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => { beforeEach(() => { const brData = mockBidRequestListData('video', 2, []); mockReferer = mockRefererData(); - request = spec.buildRequests(brData, mockReferer); + request = spec.buildRequests(brData, mockReferer)[0]; mockBidRequest = { data: brData } @@ -359,3 +403,100 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => { }); }); }); + +describe('SmartyTechDSPAdapter: buildRequests with user IDs', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestWithUserIds('banner', 2, []); + mockReferer = mockRefererData(); + }); + + it('should include userIds when available', () => { + const request = spec.buildRequests(mockBidRequest, mockReferer); + const data = request.flatMap(resp => resp.data); + + data.forEach((req, index) => { + expect(req).to.have.property('userIds'); + expect(req.userIds).to.deep.equal(mockBidRequest[index].userIdAsEids); + }); + }); + + it('should not include userIds when not available', () => { + const bidRequestWithoutUserIds = mockBidRequestListData('banner', 2, []); + const request = spec.buildRequests(bidRequestWithoutUserIds, mockReferer); + const data = request.flatMap(resp => resp.data); + + data.forEach((req) => { + expect(req).to.not.have.property('userIds'); + }); + }); + + it('should not include userIds when userIdAsEids is undefined', () => { + const bidRequestWithUndefinedUserIds = mockBidRequestListData('banner', 2, []).map(req => { + const {userIdAsEids, ...requestWithoutUserIds} = req; + return requestWithoutUserIds; + }); + const request = spec.buildRequests(bidRequestWithUndefinedUserIds, mockReferer); + const data = request.flatMap(resp => resp.data); + + data.forEach((req) => { + expect(req).to.not.have.property('userIds'); + }); + }); + + it('should not include userIds when userIdAsEids is empty array', () => { + const bidRequestWithEmptyUserIds = mockBidRequestListData('banner', 2, []).map(req => ({ + ...req, + userIdAsEids: [] + })); + const request = spec.buildRequests(bidRequestWithEmptyUserIds, mockReferer); + const data = request.flatMap(resp => resp.data); + + data.forEach((req) => { + expect(req).to.not.have.property('userIds'); + }); + }); +}); + +describe('SmartyTechDSPAdapter: buildRequests with consent data', () => { + let mockBidRequest; + let mockBidderRequest; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('banner', 2, []); + mockBidderRequest = mockBidderRequestWithConsents(); + }); + + it('should include GDPR consent when available', () => { + const request = spec.buildRequests(mockBidRequest, mockBidderRequest); + const data = request.flatMap(resp => resp.data); + + data.forEach((req) => { + expect(req).to.have.property('gdprConsent'); + expect(req.gdprConsent.gdprApplies).to.be.true; + expect(req.gdprConsent.consentString).to.equal('COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA'); + expect(req.gdprConsent.addtlConsent).to.equal('1~1.35.41.101'); + }); + }); + + it('should include USP consent when available', () => { + const request = spec.buildRequests(mockBidRequest, mockBidderRequest); + const data = request.flatMap(resp => resp.data); + + data.forEach((req) => { + expect(req).to.have.property('uspConsent'); + expect(req.uspConsent).to.equal('1YNN'); + }); + }); + + it('should not include consent data when not available', () => { + const mockReferer = mockRefererData(); + const request = spec.buildRequests(mockBidRequest, mockReferer); + const data = request.flatMap(resp => resp.data); + + data.forEach((req) => { + expect(req).to.not.have.property('gdprConsent'); + expect(req).to.not.have.property('uspConsent'); + }); + }); +}); From 18df896e2b7f48ae271db6c07cb086bd56705901 Mon Sep 17 00:00:00 2001 From: Likhith Kumar <111145706+Likhith329@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:01:59 +0530 Subject: [PATCH 020/147] Datawrkz Analytics Adapter: add publisherId and apiKey options (#13899) * feat(analytics): added Datawrkz analytics adapter * fix(analytics): lint fixes for Datawrkz Analytics Adapter * fix(analytics): lint fixes for Datawrkz Analytics Adapter * feat(analytics): add publisherId and apiKey config in Datawrkz Analytics Adapter * Datawrkz Analytics Adapter: add tests for publisherId and apiKey config --------- Co-authored-by: Monis Qadri Co-authored-by: Love Sharma --- modules/datawrkzAnalyticsAdapter.js | 51 +++++++++---------- modules/datawrkzAnalyticsAdapter.md | 7 ++- .../modules/datawrkzAnalyticsAdapter_spec.js | 14 ++++- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/modules/datawrkzAnalyticsAdapter.js b/modules/datawrkzAnalyticsAdapter.js index 3a80b15f6cd..94c2f670a5e 100644 --- a/modules/datawrkzAnalyticsAdapter.js +++ b/modules/datawrkzAnalyticsAdapter.js @@ -5,6 +5,7 @@ import { logInfo, logError } from '../src/utils.js'; let ENDPOINT = 'https://prebid-api.highr.ai/analytics'; const auctions = {}; +const adapterConfig = {}; const datawrkzAnalyticsAdapter = Object.assign(adapter({ url: ENDPOINT, analyticsType: 'endpoint' }), { @@ -125,15 +126,7 @@ const datawrkzAnalyticsAdapter = Object.assign(adapter({ url: ENDPOINT, analytic failureMessage: null, } - try { - fetch(ENDPOINT, { - method: 'POST', - body: JSON.stringify(payload), - headers: { 'Content-Type': 'application/json' } - }); - } catch (e) { - logError('[DatawrkzAnalytics] Failed to send AD_RENDER_SUCCEEDED event', e, payload); - } + this.sendToEndPoint(payload) break; } @@ -157,15 +150,7 @@ const datawrkzAnalyticsAdapter = Object.assign(adapter({ url: ENDPOINT, analytic failureMessage: message } - try { - fetch(ENDPOINT, { - method: 'POST', - body: JSON.stringify(payload), - headers: { 'Content-Type': 'application/json' } - }); - } catch (e) { - logError('[DatawrkzAnalytics] Failed to send AD_RENDER_FAILED event', e, payload); - } + this.sendToEndPoint(payload) break; } @@ -189,15 +174,7 @@ const datawrkzAnalyticsAdapter = Object.assign(adapter({ url: ENDPOINT, analytic adunits: adunitsArray }; - try { - fetch(ENDPOINT, { - method: 'POST', - body: JSON.stringify(payload), - headers: { 'Content-Type': 'application/json' } - }); - } catch (e) { - logError('[DatawrkzAnalytics] Sending failed', e, payload); - } + this.sendToEndPoint(payload) delete auctions[auctionId]; }, 2000); // Wait 2 seconds for BID_WON to happen @@ -208,6 +185,25 @@ const datawrkzAnalyticsAdapter = Object.assign(adapter({ url: ENDPOINT, analytic default: break; } + }, + sendToEndPoint(payload) { + if (!adapterConfig.publisherId || !adapterConfig.apiKey) { + logError('[DatawrkzAnalytics] Missing mandatory config: publisherId or apiKey. Skipping event.'); + return; + } + + payload.publisherId = adapterConfig.publisherId + payload.apiKey = adapterConfig.apiKey + + try { + fetch(ENDPOINT, { + method: 'POST', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' } + }); + } catch (e) { + logError('[DatawrkzAnalytics] Failed to send event', e, payload); + } } } ); @@ -215,6 +211,7 @@ const datawrkzAnalyticsAdapter = Object.assign(adapter({ url: ENDPOINT, analytic datawrkzAnalyticsAdapter.originEnableAnalytics = datawrkzAnalyticsAdapter.enableAnalytics; datawrkzAnalyticsAdapter.enableAnalytics = function (config) { + Object.assign(adapterConfig, config?.options || {}); datawrkzAnalyticsAdapter.originEnableAnalytics(config); logInfo('[DatawrkzAnalytics] Enabled with config:', config); }; diff --git a/modules/datawrkzAnalyticsAdapter.md b/modules/datawrkzAnalyticsAdapter.md index b4f7185d9be..d944b656038 100644 --- a/modules/datawrkzAnalyticsAdapter.md +++ b/modules/datawrkzAnalyticsAdapter.md @@ -19,5 +19,10 @@ Enable the adapter using: ```js pbjs.enableAnalytics({ - provider: 'datawrkzanalytics' + provider: 'datawrkzanalytics', + options: { + publisherId: 'YOUR_PUBLISHER_ID', + apiKey: 'YOUR_API_KEY' + } }); +``` diff --git a/test/spec/modules/datawrkzAnalyticsAdapter_spec.js b/test/spec/modules/datawrkzAnalyticsAdapter_spec.js index 630c189a18a..1eebbfc9efb 100644 --- a/test/spec/modules/datawrkzAnalyticsAdapter_spec.js +++ b/test/spec/modules/datawrkzAnalyticsAdapter_spec.js @@ -25,7 +25,13 @@ describe("DatawrkzAnalyticsAdapter", function () { sandbox = sinon.createSandbox(); fetchStub = sandbox.stub(window, "fetch"); - adapterManager.enableAnalytics({ provider: "datawrkzanalytics" }); + adapterManager.enableAnalytics({ + provider: "datawrkzanalytics", + options: { + publisherId: "testPublisher", + apiKey: "testApiKey" + } + }); }); afterEach(function () { @@ -126,6 +132,8 @@ describe("DatawrkzAnalyticsAdapter", function () { expect(options.headers["Content-Type"]).to.equal("application/json"); const body = JSON.parse(options.body); + expect(body.publisherId).to.equal("testPublisher"); + expect(body.apiKey).to.equal("testApiKey"); expect(body.auctionId).to.equal(auctionId); expect(body.adunits[0].code).to.equal(adUnitCode); expect(body.adunits[0].bids[0].bidder).to.equal(bidder); @@ -148,6 +156,8 @@ describe("DatawrkzAnalyticsAdapter", function () { const payload = JSON.parse(options.body); expect(payload.eventType).to.equal(AD_RENDER_SUCCEEDED); + expect(payload.publisherId).to.equal("testPublisher"); + expect(payload.apiKey).to.equal("testApiKey"); expect(payload.bidderCode).to.equal("appnexus"); expect(payload.successDoc).to.be.a("string"); expect(payload.failureReason).to.be.null; @@ -170,6 +180,8 @@ describe("DatawrkzAnalyticsAdapter", function () { const payload = JSON.parse(options.body); expect(payload.eventType).to.equal(AD_RENDER_FAILED); + expect(payload.publisherId).to.equal("testPublisher"); + expect(payload.apiKey).to.equal("testApiKey"); expect(payload.bidderCode).to.equal("appnexus"); expect(payload.successDoc).to.be.null; expect(payload.failureReason).to.equal("network"); From e125239991544a91fb407aba43d28667529554a8 Mon Sep 17 00:00:00 2001 From: andreafassina <127768714+andreafassina@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:46:38 +0200 Subject: [PATCH 021/147] Nativery Bid Adapter: track auction events (#13990) * feat: track auction events * style: lint files * perf: add keepalive on tracking request * fix: ajax content type * test: ajax content type * perf: remove json content type to avoid preflight request --------- Co-authored-by: Andrea Fassina --- modules/nativeryBidAdapter.js | 58 +++++++++++- test/spec/modules/nativeryBidAdapter_spec.js | 99 ++++++++++++++++++++ 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/modules/nativeryBidAdapter.js b/modules/nativeryBidAdapter.js index caeaa891e5e..3b3dadd1d10 100644 --- a/modules/nativeryBidAdapter.js +++ b/modules/nativeryBidAdapter.js @@ -6,7 +6,9 @@ import { deepSetValue, logError, logWarn, + safeJSONEncode, } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -18,6 +20,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'nativery'; const BIDDER_ALIAS = ['nat']; const ENDPOINT = 'https://hb.nativery.com/openrtb2/auction'; +const EVENT_TRACKER_URL = 'https://hb.nativery.com/openrtb2/track-event'; +// Currently we log every event +const DEFAULT_SAMPLING_RATE = 1; +const EVENT_LOG_RANDOM_NUMBER = Math.random(); const DEFAULT_CURRENCY = 'EUR'; const TTL = 30; const MAX_IMPS_PER_REQUEST = 10; @@ -86,7 +92,7 @@ export const spec = { ); if (Array.isArray(responseErrors) && responseErrors.length > 0) { logWarn( - 'Nativery: Error in bid response ' + JSON.stringify(responseErrors) + 'Nativery: Error in bid response ' + safeJSONEncode(responseErrors) ); } const ortb = converter.fromORTB({ @@ -96,12 +102,45 @@ export const spec = { return ortb.bids ?? []; } } catch (error) { - const errMsg = error?.message ?? JSON.stringify(error); + const errMsg = error?.message ?? safeJSONEncode(error); logError('Nativery: unhandled error in bid response ' + errMsg); return []; } return []; }, + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function(bid) { + if (bid == null || Object.keys(bid).length === 0) return + reportEvent('NAT_BID_WON', bid) + }, + /** + * Register bidder specific code, which will execute if the ad + * has been rendered successfully + * @param {Bid} bid Bid request object + */ + onAdRenderSucceeded: function (bid) { + if (bid == null || Object.keys(bid).length === 0) return + reportEvent('NAT_AD_RENDERED', bid) + }, + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {Object} timeoutData Containing timeout specific data + */ + onTimeout: function (timeoutData) { + if (!Array.isArray(timeoutData) || timeoutData.length === 0) return + reportEvent('NAT_TIMEOUT', timeoutData) + }, + /** + * Register bidder specific code, which will execute if the bidder responded with an error + * @param {Object} errorData An object with the XMLHttpRequest error and the bid request object + */ + onBidderError: function (errorData) { + if (errorData == null || Object.keys(errorData).length === 0) return + reportEvent('NAT_BIDDER_ERROR', errorData) + } }; function formatRequest(ortbPayload) { @@ -132,4 +171,19 @@ function formatRequest(ortbPayload) { return request; } +function reportEvent(event, data, sampling = null) { + // Currently this condition is always true since DEFAULT_SAMPLING_RATE = 1, + // meaning we log every event. In the future, we may want to implement event + // sampling by lowering the sampling rate. + const samplingRate = sampling ?? DEFAULT_SAMPLING_RATE; + if (samplingRate > EVENT_LOG_RANDOM_NUMBER) { + const payload = { + prebidVersion: '$prebid.version$', + event, + data, + }; + ajax(EVENT_TRACKER_URL, undefined, safeJSONEncode(payload), { method: 'POST', withCredentials: true, keepalive: true }); + } +} + registerBidder(spec); diff --git a/test/spec/modules/nativeryBidAdapter_spec.js b/test/spec/modules/nativeryBidAdapter_spec.js index e8706711b10..3aa4b90cba2 100644 --- a/test/spec/modules/nativeryBidAdapter_spec.js +++ b/test/spec/modules/nativeryBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec, converter } from 'modules/nativeryBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; +import * as ajax from 'src/ajax.js'; const ENDPOINT = 'https://hb.nativery.com/openrtb2/auction'; const MAX_IMPS_PER_REQUEST = 10; @@ -192,4 +193,102 @@ describe('NativeryAdapter', function () { logErrorSpy.restore(); }); }); + + describe('onBidWon callback', () => { + it('should exists and be a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should NOT call ajax when invalid or empty data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + spec.onBidWon(null); + spec.onBidWon({}); + spec.onBidWon(undefined); + expect(ajaxStub.called).to.be.false; + }); + it('should call ajax with correct payload when valid data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + const validData = { bidder: 'nativery', adUnitCode: 'div-1' }; + spec.onBidWon(validData); + assertTrackEvent(ajaxStub, 'NAT_BID_WON', validData) + }); + }); + + describe('onAdRenderSucceeded callback', () => { + it('should exists and be a function', () => { + expect(spec.onAdRenderSucceeded).to.exist.and.to.be.a('function'); + }); + it('should NOT call ajax when invalid or empty data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + spec.onAdRenderSucceeded(null); + spec.onAdRenderSucceeded({}); + spec.onAdRenderSucceeded(undefined); + expect(ajaxStub.called).to.be.false; + }); + it('should call ajax with correct payload when valid data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + const validData = { bidder: 'nativery', adUnitCode: 'div-1' }; + spec.onAdRenderSucceeded(validData); + assertTrackEvent(ajaxStub, 'NAT_AD_RENDERED', validData) + }); + }); + + describe('onTimeout callback', () => { + it('should exists and be a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should NOT call ajax when invalid or empty data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + spec.onTimeout(null); + spec.onTimeout({}); + spec.onTimeout([]); + spec.onTimeout(undefined); + expect(ajaxStub.called).to.be.false; + }); + it('should call ajax with correct payload when valid data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + const validData = [{ bidder: 'nativery', adUnitCode: 'div-1' }]; + spec.onTimeout(validData); + assertTrackEvent(ajaxStub, 'NAT_TIMEOUT', validData) + }); + }); + + describe('onBidderError callback', () => { + it('should exists and be a function', () => { + expect(spec.onBidderError).to.exist.and.to.be.a('function'); + }); + it('should NOT call ajax when invalid or empty data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + spec.onBidderError(null); + spec.onBidderError({}); + spec.onBidderError(undefined); + expect(ajaxStub.called).to.be.false; + }); + it('should call ajax with correct payload when valid data is provided', () => { + const ajaxStub = sandBox.stub(ajax, 'ajax'); + const validData = { + error: 'error', + bidderRequest: { + bidder: 'nativery', + } + }; + spec.onBidderError(validData); + assertTrackEvent(ajaxStub, 'NAT_BIDDER_ERROR', validData) + }); + }); }); + +const assertTrackEvent = (ajaxStub, event, data) => { + expect(ajaxStub.calledOnce).to.be.true; + + const [url, callback, body, options] = ajaxStub.firstCall.args; + + expect(url).to.equal('https://hb.nativery.com/openrtb2/track-event'); + expect(callback).to.be.undefined; + expect(body).to.be.a('string'); + expect(options).to.deep.equal({ method: 'POST', withCredentials: true, keepalive: true }); + + const payload = JSON.parse(body); + expect(payload.event).to.equal(event); + expect(payload.prebidVersion).to.exist.and.to.be.a('string') + expect(payload.data).to.deep.equal(data); +} From d43effa45d0604eae85bf2180563287037bf1fc1 Mon Sep 17 00:00:00 2001 From: Anna Yablonsky Date: Fri, 17 Oct 2025 11:03:26 +0300 Subject: [PATCH 022/147] Omnidex update details (#14021) * adding glvid to the omnidex * fixing test --- modules/omnidexBidAdapter.js | 4 +++- test/spec/modules/programmaticXBidAdapter_spec.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/omnidexBidAdapter.js b/modules/omnidexBidAdapter.js index a72234bd521..6fd1f34cf21 100644 --- a/modules/omnidexBidAdapter.js +++ b/modules/omnidexBidAdapter.js @@ -12,6 +12,7 @@ import { const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'omnidex'; const BIDDER_VERSION = '1.0.0'; +const GVLID = 1463; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { @@ -41,7 +42,8 @@ export const spec = { buildRequests, interpretResponse, getUserSyncs, - onBidWon + onBidWon, + gvlid: GVLID, }; registerBidder(spec); diff --git a/test/spec/modules/programmaticXBidAdapter_spec.js b/test/spec/modules/programmaticXBidAdapter_spec.js index ef7f0caf797..2c857efc8fb 100644 --- a/test/spec/modules/programmaticXBidAdapter_spec.js +++ b/test/spec/modules/programmaticXBidAdapter_spec.js @@ -242,6 +242,10 @@ describe('programmaticXBidAdapter', function () { expect(adapter.code).to.exist.and.to.be.a('string'); }); + it('exists and is a number', function () { + expect(adapter.gvlid).to.exist.and.to.be.a('number'); + }) + 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]); From 28245b1ad8b88e65ffb7c4e4517650a761a54feb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 06:34:58 -0400 Subject: [PATCH 023/147] Bump core-js from 3.45.1 to 3.46.0 (#14007) Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.45.1 to 3.46.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.46.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-version: 3.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b331152a46..581158ac134 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@babel/preset-env": "^7.28.3", "@babel/preset-typescript": "^7.26.0", "@babel/runtime": "^7.28.4", - "core-js": "^3.45.1", + "core-js": "^3.46.0", "crypto-js": "^4.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", @@ -8229,9 +8229,9 @@ } }, "node_modules/core-js": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", - "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", "hasInstallScript": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 355521b47b6..5ebf8b3c9f3 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@babel/preset-env": "^7.28.3", "@babel/preset-typescript": "^7.26.0", "@babel/runtime": "^7.28.4", - "core-js": "^3.45.1", + "core-js": "^3.46.0", "crypto-js": "^4.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", From bb883898573deb61acdb556070f750ec4dbc90a8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 17 Oct 2025 06:52:32 -0400 Subject: [PATCH 024/147] Core: fix bug where commands submitted to que.push can run out of order (#14025) --- src/prebid.ts | 34 +++++++++++++++++------- test/spec/unit/pbjs_api_spec.js | 47 ++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/prebid.ts b/src/prebid.ts index 4ca96f1bd1d..1f22f86915a 100644 --- a/src/prebid.ts +++ b/src/prebid.ts @@ -1168,6 +1168,14 @@ addApiMethod('setBidderConfig', config.setBidderConfig); pbjsInstance.que.push(() => listenMessagesFromCreative()); +let queSetupComplete; + +export function resetQueSetup() { + queSetupComplete = defer(); +} + +resetQueSetup(); + /** * This queue lets users load Prebid asynchronously, but run functions the same way regardless of whether it gets loaded * before or after their script executes. For example, given the code: @@ -1189,15 +1197,17 @@ pbjsInstance.que.push(() => listenMessagesFromCreative()); * @alias module:pbjs.que.push */ function quePush(command) { - if (typeof command === 'function') { - try { - command.call(); - } catch (e) { - logError('Error processing command :', e.message, e.stack); + queSetupComplete.promise.then(() => { + if (typeof command === 'function') { + try { + command.call(); + } catch (e) { + logError('Error processing command :', e.message, e.stack); + } + } else { + logError(`Commands written into ${getGlobalVarName()}.cmd.push must be wrapped in a function`); } - } else { - logError(`Commands written into ${getGlobalVarName()}.cmd.push must be wrapped in a function`); - } + }) } async function _processQueue(queue) { @@ -1223,8 +1233,12 @@ const processQueue = delayIfPrerendering(() => pbjsInstance.delayPrerendering, a pbjsInstance.que.push = pbjsInstance.cmd.push = quePush; insertLocatorFrame(); hook.ready(); - await _processQueue(pbjsInstance.que); - await _processQueue(pbjsInstance.cmd); + try { + await _processQueue(pbjsInstance.que); + await _processQueue(pbjsInstance.cmd); + } finally { + queSetupComplete.resolve(); + } }) addApiMethod('processQueue', processQueue, false); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index fbdc1cbc24e..5d852ef4e2e 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -16,7 +16,7 @@ import * as auctionModule from 'src/auction.js'; import {resetAuctionState} from 'src/auction.js'; import {registerBidder} from 'src/adapters/bidderFactory.js'; import * as pbjsModule from 'src/prebid.js'; -import pbjs, {startAuction} from 'src/prebid.js'; +import pbjs, {resetQueSetup, startAuction} from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {reset as resetDebugging} from '../../../src/debugging.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; @@ -246,20 +246,34 @@ describe('Unit: Prebid Module', function () { beforeEach(() => { ran = false; queue = pbjs[prop] = []; + resetQueSetup(); }); after(() => { pbjs.processQueue(); }) - function pushToQueue() { - queue.push(() => { ran = true }); + function pushToQueue(fn = () => { ran = true }) { + return new Promise((resolve) => { + queue.push(() => { + fn(); + resolve(); + }); + }) } - it(`should patch .push`, () => { + it(`should patch .push`, async () => { pbjs.processQueue(); - pushToQueue(); + await pushToQueue(); expect(ran).to.be.true; }); + + it('should respect insertion order', async () => { + const log = []; + pushToQueue(() => log.push(1)); + pbjs.processQueue(); + await pushToQueue(() => log.push(2)); + expect(log).to.eql([1, 2]); + }); }) }); }) @@ -3859,19 +3873,32 @@ describe('Unit: Prebid Module', function () { utils.logError.restore(); }); - it('should run commands which are pushed into it', function() { + function push(cmd) { + return new Promise((resolve) => { + pbjs.cmd.push(() => { + try { + cmd(); + } finally { + resolve(); + } + }) + }) + } + + it('should run commands which are pushed into it', async function () { const cmd = sinon.spy(); - pbjs.cmd.push(cmd); + await push(cmd); assert.isTrue(cmd.called); }); - it('should log an error when given non-functions', function() { + it('should log an error when given non-functions', async function () { pbjs.cmd.push(5); + await push(() => null); assert.isTrue(utils.logError.calledOnce); }); - it('should log an error if the command passed into it fails', function() { - pbjs.cmd.push(function() { + it('should log an error if the command passed into it fails', async function () { + await push(function () { throw new Error('Failed function.'); }); assert.isTrue(utils.logError.calledOnce); From e9748d7eee2ef7c90d5080be6337b7392094debf Mon Sep 17 00:00:00 2001 From: Alexandr Kim <47887567+alexandr-kim-vl@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:40:36 +0500 Subject: [PATCH 025/147] Semantiq RTD module: fix incorrect property name (#14027) Co-authored-by: Alexandr Kim --- modules/semantiqRtdProvider.js | 2 +- test/spec/modules/semantiqRtdProvider_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/semantiqRtdProvider.js b/modules/semantiqRtdProvider.js index 3ee6d986cce..0308735aaa8 100644 --- a/modules/semantiqRtdProvider.js +++ b/modules/semantiqRtdProvider.js @@ -160,7 +160,7 @@ const dispatchPageImpressionEvent = (companyId) => { event_type: 'pageImpression', page_impression_id: pageImpressionId, source: 'semantiqPrebidModule', - url: pageUrl, + page_url: pageUrl, }; return fetch(EVENT_COLLECTOR_URL, { diff --git a/test/spec/modules/semantiqRtdProvider_spec.js b/test/spec/modules/semantiqRtdProvider_spec.js index 8eef7a1fc34..d2c0e506edf 100644 --- a/test/spec/modules/semantiqRtdProvider_spec.js +++ b/test/spec/modules/semantiqRtdProvider_spec.js @@ -44,7 +44,7 @@ describe('semantiqRtdProvider', () => { expect(body.event_type).to.be.equal('pageImpression'); expect(body.page_impression_id).not.to.be.empty; expect(body.source).to.be.equal('semantiqPrebidModule'); - expect(body.url).to.be.equal('https://example.com/article'); + expect(body.page_url).to.be.equal('https://example.com/article'); }); it('uses the correct company ID', () => { From 28a783b547a1de4a0b3c5b67998ca37a01cc47ec Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 17 Oct 2025 12:59:48 -0400 Subject: [PATCH 026/147] Build system: add metadata override for uniquestWidget (#14031) --- metadata/overrides.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metadata/overrides.mjs b/metadata/overrides.mjs index 869069f94d3..bb722cfd4f2 100644 --- a/metadata/overrides.mjs +++ b/metadata/overrides.mjs @@ -16,5 +16,6 @@ export default { operaadsIdSystem: 'operaId', relevadRtdProvider: 'RelevadRTDModule', sirdataRtdProvider: 'SirdataRTDModule', - fanBidAdapter: 'freedomadnetwork' + fanBidAdapter: 'freedomadnetwork', + uniquestWidgetBidAdapter: 'uniquest_widget' } From 5b961a97d861db06da7df69b63e9a74d50513d4c Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 17 Oct 2025 13:14:58 -0400 Subject: [PATCH 027/147] Build system: revert dependabot updates, use browserstack's action (#14026) * Build system: adjust karma timeouts * use browserstack own action for setup * use 127.0.0.1 instead of localhost * revert dependency changes * disable dependabot * Undo timeout changes * increase disconnect tolerance * fix version and schema-utils in package.json * fix versions (again) * Allow dependabot updates to type definitions * Change dependency update schedule to monthly Changed the update schedule for GitHub Actions and npm dependencies from weekly to monthly. Added additional dependencies to the allow list. * Update Babel dependencies in dependabot configuration * Change npm dependency update interval to quarterly --------- Co-authored-by: Patrick McCann --- .github/dependabot.yml | 13 +- .github/workflows/test-chunk.yml | 19 +- .github/workflows/test.yml | 24 +- karma.conf.maker.js | 2 +- package-lock.json | 1308 ++++++++++-------------------- package.json | 30 +- 6 files changed, 470 insertions(+), 926 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e626632d1ef..12dc0c634ea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,12 +3,21 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" + interval: "monthly" - package-ecosystem: "npm" directory: "/" schedule: - interval: "weekly" + interval: "quarterly" versioning-strategy: increase + allow: + - dependency-name: 'iab-adcom' + - dependency-name: 'iab-native' + - dependency-name: 'iab-openrtb' + - dependency-name: '@types/*' + - dependency-name: '@eslint/compat' + - dependency-name: 'eslint' + - dependency-name: '@babel/*' + - dependency-name: 'webpack' ignore: - dependency-name: "*" update-types: ["version-update:semver-major"] diff --git a/.github/workflows/test-chunk.yml b/.github/workflows/test-chunk.yml index ef5e26729e0..1ad7d91055c 100644 --- a/.github/workflows/test-chunk.yml +++ b/.github/workflows/test-chunk.yml @@ -55,9 +55,17 @@ jobs: key: ${{ inputs.wdir }} fail-on-cache-miss: true - - name: Start BrowserstackLocal - run: | - ./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY --daemon start + - name: 'BrowserStack Env Setup' + uses: 'browserstack/github-actions/setup-env@master' + with: + username: ${{ secrets.BROWSERSTACK_USER_NAME}} + access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + + - name: 'BrowserStackLocal Setup' + uses: 'browserstack/github-actions/setup-local@master' + with: + local-testing: start + local-identifier: random - name: Run tests uses: nick-fields/retry@v3 @@ -66,6 +74,11 @@ jobs: max_attempts: 1 command: ${{ inputs.cmd }} + - name: 'BrowserStackLocal Stop' + uses: 'browserstack/github-actions/setup-local@master' + with: + local-testing: stop + - name: Save working directory uses: actions/cache/save@v4 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2fc4e434240..f3329ea607d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,11 +55,6 @@ jobs: - name: Install dependencies run: npm ci - - name: Download Browserstack binary - run: | - wget https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip - unzip BrowserStackLocal-linux-x64.zip - - name: Cache source uses: actions/cache/save@v4 with: @@ -138,9 +133,17 @@ jobs: key: source-${{ github.run_id }} fail-on-cache-miss: true - - name: Start BrowserstackLocal - run: | - ./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY --daemon start + - name: 'BrowserStack Env Setup' + uses: 'browserstack/github-actions/setup-env@master' + with: + username: ${{ secrets.BROWSERSTACK_USER_NAME}} + access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + + - name: 'BrowserStackLocal Setup' + uses: 'browserstack/github-actions/setup-local@master' + with: + local-testing: start + local-identifier: random - name: Run tests uses: nick-fields/retry@v3 @@ -149,6 +152,11 @@ jobs: max_attempts: 1 command: npx gulp e2e-test + - name: 'BrowserStackLocal Stop' + uses: 'browserstack/github-actions/setup-local@master' + with: + local-testing: stop + coveralls: name: Update coveralls needs: [checkout, test] diff --git a/karma.conf.maker.js b/karma.conf.maker.js index ce7110def58..1068e9828d8 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -177,7 +177,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe browserDisconnectTimeout: 1e5, // default 2000 browserNoActivityTimeout: 1e5, // default 10000 captureTimeout: 3e5, // default 60000, - browserDisconnectTolerance: 1, + browserDisconnectTolerance: 3, concurrency: 5, // browserstack allows us 5 concurrent sessions plugins: plugins diff --git a/package-lock.json b/package-lock.json index 581158ac134..59a774f9d98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "10.13.0-pre", "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.28.4", + "@babel/core": "^7.28.3", "@babel/plugin-transform-runtime": "^7.18.9", - "@babel/preset-env": "^7.28.3", + "@babel/preset-env": "^7.27.2", "@babel/preset-typescript": "^7.26.0", - "@babel/runtime": "^7.28.4", - "core-js": "^3.46.0", + "@babel/runtime": "^7.28.3", + "core-js": "^3.45.1", "crypto-js": "^4.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", @@ -29,11 +29,11 @@ "live-connect-js": "^7.2.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.28.4", + "@babel/eslint-parser": "^7.16.5", "@babel/plugin-transform-runtime": "^7.27.4", "@babel/register": "^7.28.3", - "@eslint/compat": "^1.4.0", - "@types/google-publisher-tag": "^1.20250811.1", + "@eslint/compat": "^1.3.1", + "@types/google-publisher-tag": "^1.20250210.0", "@wdio/browserstack-service": "^9.19.1", "@wdio/cli": "^9.19.1", "@wdio/concise-reporter": "^8.29.0", @@ -46,14 +46,14 @@ "body-parser": "^1.19.0", "chai": "^4.2.0", "deep-equal": "^2.0.3", - "eslint": "^9.35.0", + "eslint": "^9.31.0", "eslint-plugin-chai-friendly": "^1.1.0", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsdoc": "^50.6.6", "execa": "^1.0.0", "faker": "^5.5.3", "fancy-log": "^2.0.0", - "fs-extra": "^11.3.2", + "fs-extra": "^11.3.1", "globals": "^16.3.0", "gulp": "^5.0.1", "gulp-clean": "^0.4.0", @@ -83,7 +83,7 @@ "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.4.0", - "karma-spec-reporter": "^0.0.36", + "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", "merge-stream": "^2.0.0", @@ -94,22 +94,22 @@ "node-html-parser": "^6.1.5", "opn": "^5.4.0", "plugin-error": "^2.0.1", - "puppeteer": "^24.18.0", + "puppeteer": "^24.10.0", "resolve-from": "^5.0.0", "sinon": "^20.0.0", "source-map-loader": "^5.0.0", "through2": "^4.0.2", "typescript": "^5.8.2", "typescript-eslint": "^8.26.1", - "url": "^0.11.4", + "url": "^0.11.0", "url-parse": "^1.0.5", "video.js": "^7.21.7", "videojs-contrib-ads": "^6.9.0", "videojs-ima": "^2.4.0", "videojs-playlist": "^5.2.0", "webdriver": "^9.19.2", - "webdriverio": "^9.20.0", - "webpack": "^5.101.3", + "webdriverio": "^9.18.4", + "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.1", "webpack-stream": "^7.0.0", @@ -125,6 +125,17 @@ "schema-utils": "^4.3.2" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "license": "MIT", @@ -138,30 +149,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "version": "7.27.5", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "license": "MIT", "dependencies": { + "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -177,9 +186,7 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz", - "integrity": "sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA==", + "version": "7.24.7", "dev": true, "license": "MIT", "dependencies": { @@ -236,17 +243,15 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "version": "7.27.1", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", + "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "engines": { @@ -272,44 +277,19 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.4", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -450,25 +430,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -533,13 +513,11 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "version": "7.27.1", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -640,14 +618,12 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "version": "7.27.1", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -685,9 +661,7 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", - "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "version": "7.27.5", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -714,12 +688,10 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.27.1", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -730,17 +702,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "version": "7.27.1", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.27.1", + "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" @@ -749,6 +719,13 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.27.1", "license": "MIT", @@ -764,13 +741,10 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "version": "7.27.3", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -833,22 +807,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-exponentiation-operator": { "version": "7.27.1", "license": "MIT", @@ -1068,16 +1026,13 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.27.3", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/plugin-transform-destructuring": "^7.27.3", + "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1128,9 +1083,7 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "version": "7.27.1", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1185,9 +1138,7 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.27.5", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1385,12 +1336,10 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "version": "7.27.2", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.0", + "@babel/compat-data": "^7.27.2", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", @@ -1398,26 +1347,25 @@ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-block-scoping": "^7.27.1", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-destructuring": "^7.27.1", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", "@babel/plugin-transform-exponentiation-operator": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", @@ -1434,15 +1382,15 @@ "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-parameters": "^7.27.1", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regenerator": "^7.27.1", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1455,10 +1403,10 @@ "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -1468,19 +1416,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "license": "MIT", @@ -1625,10 +1560,19 @@ "semver": "bin/semver" } }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1647,17 +1591,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -1665,9 +1609,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1750,9 +1694,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", "dependencies": { @@ -1788,14 +1732,11 @@ } }, "node_modules/@eslint/compat": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.0.tgz", - "integrity": "sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.1.tgz", + "integrity": "sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.16.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1808,19 +1749,6 @@ } } }, - "node_modules/@eslint/compat/node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/config-array": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", @@ -1837,9 +1765,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1847,9 +1775,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1917,9 +1845,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", - "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { @@ -1940,13 +1868,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -2748,16 +2676,6 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "license": "MIT", @@ -2766,9 +2684,7 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "version": "0.3.6", "dev": true, "license": "MIT", "dependencies": { @@ -2936,18 +2852,17 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.8.tgz", - "integrity": "sha512-f02QYEnBDE0p8cteNoPYHHjbDuwyfbe4cCIVlNi8/MRicIxFW4w4CfgU0LNgWEID6s06P+hRJ1qjpBLMhPRCiQ==", + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "debug": "^4.4.1", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.2", - "tar-fs": "^3.1.0", + "tar-fs": "^3.0.8", "yargs": "^17.7.2" }, "bin": { @@ -3160,32 +3075,8 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.6", "dev": true, "license": "MIT" }, @@ -3200,11 +3091,10 @@ "license": "MIT" }, "node_modules/@types/google-publisher-tag": { - "version": "1.20250811.1", - "resolved": "https://registry.npmjs.org/@types/google-publisher-tag/-/google-publisher-tag-1.20250811.1.tgz", - "integrity": "sha512-35rEuUfoCENB5aCt2mQIfLomoSfGtp/DwcAZMQ1dSFyEceXEsz7TmPuRGz4lwoZiSq991ZaDz49MNawtPy5XPw==", - "dev": true, - "license": "MIT" + "version": "1.20250428.0", + "resolved": "https://registry.npmjs.org/@types/google-publisher-tag/-/google-publisher-tag-1.20250428.0.tgz", + "integrity": "sha512-W+aTMsM4e8PE/TkH/RkMbmmwEFg2si9eUugS5/lt88wkEClqcALi+3WLXW39Xgzu89+3igi/RNIpPLKdt6W7Dg==", + "dev": true }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -3969,24 +3859,6 @@ "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/config": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", - "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "deepmerge-ts": "^7.0.3", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" - }, - "engines": { - "node": ">=18.20.0" - } - }, "node_modules/@wdio/browserstack-service/node_modules/@wdio/logger": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", @@ -4004,26 +3876,6 @@ "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/protocols": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", - "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", - "dev": true, - "license": "MIT" - }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/repl": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", - "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": ">=18.20.0" - } - }, "node_modules/@wdio/browserstack-service/node_modules/@wdio/reporter": { "version": "9.19.1", "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.19.1.tgz", @@ -4054,42 +3906,6 @@ "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/utils": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", - "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.18.0", - "@wdio/types": "9.19.1", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.2", - "geckodriver": "^5.0.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "mitt": "^3.0.1", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" - }, - "engines": { - "node": ">=18.20.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/browserstack-service/node_modules/chalk": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", @@ -4113,20 +3929,6 @@ "node": ">=0.3.1" } }, - "node_modules/@wdio/browserstack-service/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/browserstack-service/node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -4141,74 +3943,6 @@ "uuid": "dist/esm/bin/uuid" } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriver": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.1.tgz", - "integrity": "sha512-cvccIZ3QaUZxxrA81a3rqqgxKt6VzVrZupMc+eX9J40qfGrV3NtdLb/m4AA1PmeTPGN5O3/4KrzDpnVZM4WUnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0", - "@types/ws": "^8.5.3", - "@wdio/config": "9.19.1", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "deepmerge-ts": "^7.0.3", - "https-proxy-agent": "^7.0.6", - "undici": "^6.21.3", - "ws": "^8.8.0" - }, - "engines": { - "node": ">=18.20.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.1.tgz", - "integrity": "sha512-hpGgK6d9QNi3AaLFWIPQaEMqJhXF048XAIsV5i5mkL0kjghV1opcuhKgbbG+7pcn8JSpiq6mh7o3MDYtapw90w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.11.30", - "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.19.1", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/repl": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "archiver": "^7.0.1", - "aria-query": "^5.3.0", - "cheerio": "^1.0.0-rc.12", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "grapheme-splitter": "^1.0.4", - "htmlfy": "^0.8.1", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "query-selector-shadow-dom": "^1.0.1", - "resq": "^1.11.0", - "rgb2hex": "0.2.5", - "serialize-error": "^12.0.0", - "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.19.1" - }, - "engines": { - "node": ">=18.20.0" - }, - "peerDependencies": { - "puppeteer-core": ">=22.x || <=24.x" - }, - "peerDependenciesMeta": { - "puppeteer-core": { - "optional": true - } - } - }, "node_modules/@wdio/cli": { "version": "9.19.1", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.19.1.tgz", @@ -4413,19 +4147,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@wdio/cli/node_modules/@wdio/repl": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", - "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": ">=18.20.0" - } - }, "node_modules/@wdio/cli/node_modules/@wdio/types": { "version": "9.19.1", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", @@ -4465,16 +4186,6 @@ "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/cli/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -4634,20 +4345,6 @@ "node": ">=8" } }, - "node_modules/@wdio/cli/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/cli/node_modules/jest-diff": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", @@ -4935,86 +4632,18 @@ "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/cli/node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/webdriver": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.1.tgz", - "integrity": "sha512-cvccIZ3QaUZxxrA81a3rqqgxKt6VzVrZupMc+eX9J40qfGrV3NtdLb/m4AA1PmeTPGN5O3/4KrzDpnVZM4WUnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0", - "@types/ws": "^8.5.3", - "@wdio/config": "9.19.1", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "deepmerge-ts": "^7.0.3", - "https-proxy-agent": "^7.0.6", - "undici": "^6.21.3", - "ws": "^8.8.0" - }, - "engines": { - "node": ">=18.20.0" + "node": ">=8" } }, - "node_modules/@wdio/cli/node_modules/webdriverio": { - "version": "9.19.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.1.tgz", - "integrity": "sha512-hpGgK6d9QNi3AaLFWIPQaEMqJhXF048XAIsV5i5mkL0kjghV1opcuhKgbbG+7pcn8JSpiq6mh7o3MDYtapw90w==", + "node_modules/@wdio/cli/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "^20.11.30", - "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.19.1", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/repl": "9.16.2", - "@wdio/types": "9.19.1", - "@wdio/utils": "9.19.1", - "archiver": "^7.0.1", - "aria-query": "^5.3.0", - "cheerio": "^1.0.0-rc.12", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "grapheme-splitter": "^1.0.4", - "htmlfy": "^0.8.1", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "query-selector-shadow-dom": "^1.0.1", - "resq": "^1.11.0", - "rgb2hex": "0.2.5", - "serialize-error": "^12.0.0", - "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.19.1" - }, + "peer": true, "engines": { - "node": ">=18.20.0" - }, - "peerDependencies": { - "puppeteer-core": ">=22.x || <=24.x" - }, - "peerDependenciesMeta": { - "puppeteer-core": { - "optional": true - } + "node": ">=14.0.0" } }, "node_modules/@wdio/cli/node_modules/yargs": { @@ -6033,73 +5662,57 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "version": "1.11.6", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "version": "1.11.6", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "version": "1.12.1", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "version": "1.11.6", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "version": "1.11.6", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "version": "1.11.6", "dev": true, "license": "MIT", "dependencies": { @@ -6107,9 +5720,7 @@ } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "version": "1.11.6", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6117,79 +5728,67 @@ } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "version": "1.11.6", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "version": "1.12.1", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -6203,15 +5802,11 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, "license": "Apache-2.0" }, @@ -6265,19 +5860,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "dev": true, @@ -6337,6 +5919,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -6353,6 +5936,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6367,7 +5951,8 @@ "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "peer": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -6743,20 +6328,16 @@ "license": "MIT" }, "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "version": "3.1.8", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" }, "engines": { "node": ">= 0.4" @@ -6807,19 +6388,16 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "version": "1.2.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6829,16 +6407,14 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "version": "1.3.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -7015,14 +6591,12 @@ } }, "node_modules/axios": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz", - "integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==", + "version": "1.9.0", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, @@ -7085,13 +6659,11 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.11", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", "semver": "^6.3.1" }, "peerDependencies": { @@ -7100,7 +6672,6 @@ }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.11.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.3", @@ -7111,12 +6682,10 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.2", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -7234,15 +6803,6 @@ "node": "^4.5.0 || >= 5.9" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", - "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, "node_modules/basic-auth": { "version": "2.0.1", "dev": true, @@ -7449,9 +7009,7 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "version": "4.25.0", "funding": [ { "type": "opencollective", @@ -7468,10 +7026,9 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { @@ -7643,9 +7200,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001746", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", - "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", + "version": "1.0.30001737", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", "funding": [ { "type": "opencollective", @@ -7792,11 +7349,10 @@ } }, "node_modules/chromium-bidi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz", - "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" @@ -7996,8 +7552,6 @@ }, "node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT" }, @@ -8229,9 +7783,9 @@ } }, "node_modules/core-js": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", - "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -8240,12 +7794,10 @@ } }, "node_modules/core-js-compat": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", - "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "version": "3.42.0", "license": "MIT", "dependencies": { - "browserslist": "^4.25.3" + "browserslist": "^4.24.4" }, "funding": { "type": "opencollective", @@ -9090,11 +8642,10 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1475386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz", - "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==", - "dev": true, - "license": "BSD-3-Clause" + "version": "0.0.1464554", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", + "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", + "dev": true }, "node_modules/di": { "version": "0.0.1", @@ -9414,9 +8965,7 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.222", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz", - "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==", + "version": "1.5.161", "license": "ISC" }, "node_modules/emoji-regex": { @@ -9508,9 +9057,7 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.17.1", "dev": true, "license": "MIT", "dependencies": { @@ -9573,9 +9120,7 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.23.9", "dev": true, "license": "MIT", "dependencies": { @@ -9583,18 +9128,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", + "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -9606,24 +9151,21 @@ "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", - "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", + "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", + "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", + "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -9632,7 +9174,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" + "which-typed-array": "^1.1.18" }, "engines": { "node": ">= 0.4" @@ -9732,16 +9274,11 @@ } }, "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -9964,20 +9501,20 @@ } }, "node_modules/eslint": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", - "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.35.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -10157,9 +9694,7 @@ "license": "MIT" }, "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "version": "2.12.0", "dev": true, "license": "MIT", "dependencies": { @@ -10176,8 +9711,6 @@ }, "node_modules/eslint-module-utils/node_modules/debug": { "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10220,30 +9753,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "version": "2.31.0", "dev": true, "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", - "is-core-module": "^2.16.1", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.1", + "object.values": "^1.2.0", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -11268,7 +10799,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "peer": true }, "node_modules/fast-xml-parser": { "version": "5.2.5", @@ -11697,9 +11229,9 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", "dev": true, "license": "MIT", "dependencies": { @@ -13526,9 +13058,7 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.15.1", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -13699,19 +13229,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -14786,8 +14303,6 @@ }, "node_modules/jest-worker": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", "dependencies": { @@ -14801,8 +14316,6 @@ }, "node_modules/jest-worker/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -14811,8 +14324,6 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -14825,16 +14336,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jiti": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", - "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -15284,13 +14785,11 @@ } }, "node_modules/karma-spec-reporter": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", - "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", + "version": "0.0.32", "dev": true, "license": "MIT", "dependencies": { - "colors": "1.4.0" + "colors": "^1.1.2" }, "peerDependencies": { "karma": ">=0.9" @@ -16747,9 +16246,7 @@ } }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.19", "license": "MIT" }, "node_modules/node-request-interceptor": { @@ -17738,18 +17235,17 @@ } }, "node_modules/puppeteer": { - "version": "24.18.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.18.0.tgz", - "integrity": "sha512-Ke8oL/87GhzKIM2Ag6Yj49t5xbGc4rspGIuSuFLFCQBtYzWqCSanvqoCu08WkI78rbqcwnHjxiTH6oDlYFrjrw==", + "version": "24.11.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.11.2.tgz", + "integrity": "sha512-HopdRZWHa5zk0HSwd8hU+GlahQ3fmesTAqMIDHVY9HasCvppcYuHYXyjml0nlm+nbwVCqAQWV+dSmiNCrZGTGQ==", "dev": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.10.8", - "chromium-bidi": "8.0.0", + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1475386", - "puppeteer-core": "24.18.0", + "devtools-protocol": "0.0.1464554", + "puppeteer-core": "24.11.2", "typed-query-selector": "^2.12.0" }, "bin": { @@ -17760,16 +17256,15 @@ } }, "node_modules/puppeteer-core": { - "version": "24.18.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.18.0.tgz", - "integrity": "sha512-As0BvfXxek2MbV0m7iqBmQKFnfSrzSvTM4zGipjd4cL+9f2Ccgut6RvHlc8+qBieKHqCMFy9BSI4QyveoYXTug==", + "version": "24.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.11.2.tgz", + "integrity": "sha512-c49WifNb8hix+gQH17TldmD6TC/Md2HBaTJLHexIUq4sZvo2pyHY/Pp25qFQjibksBu/SJRYUY7JsoaepNbiRA==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.10.8", - "chromium-bidi": "8.0.0", + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", "debug": "^4.4.1", - "devtools-protocol": "0.0.1475386", + "devtools-protocol": "0.0.1464554", "typed-query-selector": "^2.12.0", "ws": "^8.18.3" }, @@ -18234,6 +17729,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -18244,21 +17740,16 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.8", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -18539,9 +18030,10 @@ "license": "MIT" }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -18560,6 +18052,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18575,6 +18068,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -18585,7 +18079,8 @@ "node_modules/schema-utils/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "peer": true }, "node_modules/semver": { "version": "6.3.1", @@ -19241,17 +18736,6 @@ "decode-uri-component": "^0.2.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/spacetrim": { "version": "0.11.25", "dev": true, @@ -19370,14 +18854,11 @@ } }, "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" + "internal-slot": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -19811,9 +19292,7 @@ } }, "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "version": "3.0.9", "dev": true, "license": "MIT", "dependencies": { @@ -19905,14 +19384,12 @@ } }, "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "version": "5.31.1", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -19924,17 +19401,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.10", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -19958,6 +19433,32 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "dev": true, @@ -20711,17 +20212,12 @@ } }, "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "version": "0.11.3", "dev": true, "license": "MIT", "dependencies": { "punycode": "^1.4.1", - "qs": "^6.12.3" - }, - "engines": { - "node": ">= 0.4" + "qs": "^6.11.2" } }, "node_modules/url-parse": { @@ -21326,20 +20822,20 @@ } }, "node_modules/webdriverio": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.20.0.tgz", - "integrity": "sha512-cqaXfahTzCFaQLlk++feZaze6tAsW8OSdaVRgmOGJRII1z2A4uh4YGHtusTpqOiZAST7OBPqycOwfh01G/Ktbg==", + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.1.tgz", + "integrity": "sha512-hpGgK6d9QNi3AaLFWIPQaEMqJhXF048XAIsV5i5mkL0kjghV1opcuhKgbbG+7pcn8JSpiq6mh7o3MDYtapw90w==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.20.0", + "@wdio/config": "9.19.1", "@wdio/logger": "9.18.0", "@wdio/protocols": "9.16.2", "@wdio/repl": "9.16.2", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.20.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -21356,7 +20852,7 @@ "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.20.0" + "webdriver": "9.19.1" }, "engines": { "node": ">=18.20.0" @@ -21371,19 +20867,18 @@ } }, "node_modules/webdriverio/node_modules/@wdio/config": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.20.0.tgz", - "integrity": "sha512-ggwd3EMsVj/LTcbYw2h+hma+/7fQ1cTXMuy9B5WTkLjDlOtbLjsqs9QLt4BLIo1cdsxvAw/UVpRVUuYy7rTmtQ==", + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.1.tgz", + "integrity": "sha512-BeTB2paSjaij3cf1NXQzX9CZmdj5jz2/xdUhkJlCeGmGn1KjWu5BjMO+exuiy+zln7dOJjev8f0jlg8e8f1EbQ==", "dev": true, "license": "MIT", "dependencies": { "@wdio/logger": "9.18.0", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.20.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0", - "jiti": "^2.5.1" + "import-meta-resolve": "^4.0.0" }, "engines": { "node": ">=18.20.0" @@ -21427,9 +20922,9 @@ } }, "node_modules/webdriverio/node_modules/@wdio/types": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.20.0.tgz", - "integrity": "sha512-zMmAtse2UMCSOW76mvK3OejauAdcFGuKopNRH7crI0gwKTZtvV89yXWRziz9cVXpFgfmJCjf9edxKFWdhuF5yw==", + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.1.tgz", + "integrity": "sha512-Q1HVcXiWMHp3ze2NN1BvpsfEh/j6GtAeMHhHW4p2IWUfRZlZqTfiJ+95LmkwXOG2gw9yndT8NkJigAz8v7WVYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -21440,15 +20935,15 @@ } }, "node_modules/webdriverio/node_modules/@wdio/utils": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.20.0.tgz", - "integrity": "sha512-T1ze005kncUTocYImSBQc/FAVcOwP/vOU4MDJFgzz/RTcps600qcKX98sVdWM5/ukXCVkjOufWteDHIbX5/tEA==", + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.1.tgz", + "integrity": "sha512-wWx5uPCgdZQxFIemAFVk/aa3JLwqrTsvEJsPlV3lCRpLeQ67V8aUPvvNAzE+RhX67qvelwwsvX8RrPdLDfnnYw==", "dev": true, "license": "MIT", "dependencies": { "@puppeteer/browsers": "^2.2.0", "@wdio/logger": "9.18.0", - "@wdio/types": "9.20.0", + "@wdio/types": "9.19.1", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^6.1.2", @@ -21476,9 +20971,9 @@ } }, "node_modules/webdriverio/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", "dev": true, "license": "MIT", "engines": { @@ -21503,19 +20998,19 @@ } }, "node_modules/webdriverio/node_modules/webdriver": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.20.0.tgz", - "integrity": "sha512-Kk+AGV1xWLNHVpzUynQJDULMzbcO3IjXo3s0BzfC30OpGxhpaNmoazMQodhtv0Lp242Mb1VYXD89dCb4oAHc4w==", + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.1.tgz", + "integrity": "sha512-cvccIZ3QaUZxxrA81a3rqqgxKt6VzVrZupMc+eX9J40qfGrV3NtdLb/m4AA1PmeTPGN5O3/4KrzDpnVZM4WUnA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.20.0", + "@wdio/config": "9.19.1", "@wdio/logger": "9.18.0", "@wdio/protocols": "9.16.2", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.20.0", + "@wdio/types": "9.19.1", + "@wdio/utils": "9.19.1", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", @@ -21526,23 +21021,19 @@ } }, "node_modules/webpack": { - "version": "5.101.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", - "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "version": "5.94.0", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -21552,11 +21043,11 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.1", - "webpack-sources": "^3.3.3" + "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" @@ -21676,9 +21167,7 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.2.3", "dev": true, "license": "MIT", "engines": { @@ -21795,11 +21284,36 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/webpack/node_modules/acorn-import-attributes": { + "version": "1.9.5", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/webpack/node_modules/json-parse-even-better-errors": { "version": "2.3.1", "dev": true, "license": "MIT" }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "dev": true, diff --git a/package.json b/package.json index 5ebf8b3c9f3..4bde03f6a56 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,11 @@ "node": ">=20.0.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.28.4", + "@babel/eslint-parser": "^7.16.5", "@babel/plugin-transform-runtime": "^7.27.4", "@babel/register": "^7.28.3", - "@eslint/compat": "^1.4.0", - "@types/google-publisher-tag": "^1.20250811.1", + "@eslint/compat": "^1.3.1", + "@types/google-publisher-tag": "^1.20250210.0", "@wdio/browserstack-service": "^9.19.1", "@wdio/cli": "^9.19.1", "@wdio/concise-reporter": "^8.29.0", @@ -70,14 +70,14 @@ "body-parser": "^1.19.0", "chai": "^4.2.0", "deep-equal": "^2.0.3", - "eslint": "^9.35.0", + "eslint": "^9.31.0", "eslint-plugin-chai-friendly": "^1.1.0", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsdoc": "^50.6.6", "execa": "^1.0.0", "faker": "^5.5.3", "fancy-log": "^2.0.0", - "fs-extra": "^11.3.2", + "fs-extra": "^11.3.1", "globals": "^16.3.0", "gulp": "^5.0.1", "gulp-clean": "^0.4.0", @@ -107,7 +107,7 @@ "karma-script-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "karma-sourcemap-loader": "^0.4.0", - "karma-spec-reporter": "^0.0.36", + "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", "merge-stream": "^2.0.0", @@ -118,34 +118,34 @@ "node-html-parser": "^6.1.5", "opn": "^5.4.0", "plugin-error": "^2.0.1", - "puppeteer": "^24.18.0", + "puppeteer": "^24.10.0", "resolve-from": "^5.0.0", "sinon": "^20.0.0", "source-map-loader": "^5.0.0", "through2": "^4.0.2", "typescript": "^5.8.2", "typescript-eslint": "^8.26.1", - "url": "^0.11.4", + "url": "^0.11.0", "url-parse": "^1.0.5", "video.js": "^7.21.7", "videojs-contrib-ads": "^6.9.0", "videojs-ima": "^2.4.0", "videojs-playlist": "^5.2.0", "webdriver": "^9.19.2", - "webdriverio": "^9.20.0", - "webpack": "^5.101.3", + "webdriverio": "^9.18.4", + "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.1", "webpack-stream": "^7.0.0", "yargs": "^1.3.1" }, "dependencies": { - "@babel/core": "^7.28.4", + "@babel/core": "^7.28.3", "@babel/plugin-transform-runtime": "^7.18.9", - "@babel/preset-env": "^7.28.3", + "@babel/preset-env": "^7.27.2", "@babel/preset-typescript": "^7.26.0", - "@babel/runtime": "^7.28.4", - "core-js": "^3.46.0", + "@babel/runtime": "^7.28.3", + "core-js": "^3.45.1", "crypto-js": "^4.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", From fff26fb9c6026cc1ddd3744d2cbb6aab0aad7d41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:23:24 -0400 Subject: [PATCH 028/147] Bump @types/ws from 8.5.12 to 8.18.1 (#14033) Bumps [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws) from 8.5.12 to 8.18.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ws) --- updated-dependencies: - dependency-name: "@types/ws" dependency-version: 8.18.1 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 59a774f9d98..907957a3306 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3174,7 +3174,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.12", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { From cf808064632212bf997df862adb805fc80785838 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:48:14 -0400 Subject: [PATCH 029/147] Bump actions/setup-node from 5 to 6 (#14032) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node 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/code-path-changes.yml | 2 +- .github/workflows/jscpd.yml | 2 +- .github/workflows/linter.yml | 2 +- .github/workflows/run-unit-tests.yml | 2 +- .github/workflows/test-chunk.yml | 2 +- .github/workflows/test.yml | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml index 5927aae1ada..8d327a7e2b1 100644 --- a/.github/workflows/code-path-changes.yml +++ b/.github/workflows/code-path-changes.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v5 - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '18' diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 5fc6e48291f..39e54bebcf0 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -17,7 +17,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 5ef3998307b..30d327d3495 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 3089f588554..60e0713a552 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -31,7 +31,7 @@ jobs: timeout-minutes: 5 steps: - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' diff --git a/.github/workflows/test-chunk.yml b/.github/workflows/test-chunk.yml index 1ad7d91055c..12c27a370de 100644 --- a/.github/workflows/test-chunk.yml +++ b/.github/workflows/test-chunk.yml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3329ea607d..422eb16bcaf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: base-commit: ${{ steps.info.outputs.base-commit }} steps: - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' @@ -75,7 +75,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' - name: Restore source @@ -123,7 +123,7 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '20' - name: Restore source From cdd437fc3690ea34140523336faf013b9e2e08b7 Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:59:40 +0200 Subject: [PATCH 030/147] riseBidAdapter: get the user Ids from userIdAsEids (#14013) * riseBidAdapter: get the user Ids from userIdAsEids * add tests for the user id changes --------- Co-authored-by: Laslo Chechur Co-authored-by: Patrick McCann --- libraries/riseUtils/index.js | 2 +- test/spec/modules/riseBidAdapter_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index 21f2d72660f..cad9d64c746 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -385,7 +385,7 @@ export function generateGeneralParams(generalObject, bidderRequest, adapterVersi tmax: timeout }; - const userIdsParam = getBidIdParameter('userId', generalObject); + const userIdsParam = getBidIdParameter('userIdAsEids', generalObject); if (userIdsParam) { generalParams.userIds = JSON.stringify(userIdsParam); } diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 4f5de5a651e..bc7446a448c 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -543,6 +543,29 @@ describe('riseAdapter', function () { expect(request.data.bids[0].coppa).to.be.equal(1); }); }); + + describe('User Eids', function() { + it('should get the Eids from the userIdAsEids object and set them in the request', function() { + const bid = utils.deepClone(bidRequests[0]); + const userIds = [ + { + sourcer: 'pubcid.org', + uids: [{ + id: '12345678', + atype: 1, + }] + }]; + bid.userIdAsEids = userIds; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.params.userIds).to.be.equal(JSON.stringify(userIds)); + }); + + it('should not set the userIds request param if no userIdAsEids are set', function() { + const bid = utils.deepClone(bidRequests[0]); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.params.userIds).to.be.undefined; + }); + }); }); describe('interpretResponse', function () { From 05a969fdd812d8227b33f0a48bb0942a081cc0a4 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 17 Oct 2025 14:06:39 -0400 Subject: [PATCH 031/147] Core: break out dependabot security and version update rules (#14037) Added configuration for daily security updates with no open pull requests limit. --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 12dc0c634ea..80304287ef7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,7 @@ updates: directory: "/" schedule: interval: "quarterly" + open-pull-requests-limit: 2 versioning-strategy: increase allow: - dependency-name: 'iab-adcom' @@ -21,3 +22,12 @@ updates: ignore: - dependency-name: "*" update-types: ["version-update:semver-major"] + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 0 + groups: + all-security: + applies-to: security-updates + patterns: ["*"] From c32cb703e864d82b4859a2074582154256ba29b3 Mon Sep 17 00:00:00 2001 From: nuba-io Date: Fri, 17 Oct 2025 22:23:38 +0300 Subject: [PATCH 032/147] Nuba Bid Adapter: initial release (#14003) * feature/nuba-bidder: add Prebid.js bidder adapter * feature/nuba-bidder * feature/nuba-bidder * feature/nuba-bidder --------- Co-authored-by: Monis Qadri --- modules/nubaBidAdapter.js | 19 + modules/nubaBidAdapter.md | 79 ++++ test/spec/modules/nubaBidAdapter_spec.js | 475 +++++++++++++++++++++++ 3 files changed, 573 insertions(+) create mode 100644 modules/nubaBidAdapter.js create mode 100644 modules/nubaBidAdapter.md create mode 100644 test/spec/modules/nubaBidAdapter_spec.js diff --git a/modules/nubaBidAdapter.js b/modules/nubaBidAdapter.js new file mode 100644 index 00000000000..0ebfe715508 --- /dev/null +++ b/modules/nubaBidAdapter.js @@ -0,0 +1,19 @@ +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 = 'nuba'; + +const AD_URL = 'https://ads.nuba.io/openrtb2/auction'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: () => {}, +}; + +registerBidder(spec); diff --git a/modules/nubaBidAdapter.md b/modules/nubaBidAdapter.md new file mode 100644 index 00000000000..6f1500e6ab1 --- /dev/null +++ b/modules/nubaBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Nuba Bidder Adapter +Module Type: Nuba Bidder Adapter +Maintainer: ssp@nuba.io +``` + +# Description + +Connects to Nuba.io exchange for bids. +Nuba.io 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: 'nuba', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'nuba', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'nuba', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/nubaBidAdapter_spec.js b/test/spec/modules/nubaBidAdapter_spec.js new file mode 100644 index 00000000000..ad08015455e --- /dev/null +++ b/test/spec/modules/nubaBidAdapter_spec.js @@ -0,0 +1,475 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/nubaBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'nuba'; + +describe('NubaBidAdapter', 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 b3138c87021049887630473d51c1a345f49f77d4 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 17 Oct 2025 20:47:31 +0000 Subject: [PATCH 033/147] Prebid 10.13.0 release --- ...utogen_2d_RenderingContext_getImageData.ql | 2 +- ...togen_2d_RenderingContext_isPointInPath.ql | 2 +- ...autogen_2d_RenderingContext_measureText.ql | 2 +- .../queries/autogen_AudioWorkletNode.ql | 15 ---- .../queries/autogen_Date_getTimezoneOffset.ql | 16 ---- .../autogen_DeviceMotionEvent_acceleration.ql | 2 +- ...otionEvent_accelerationIncludingGravity.ql | 2 +- .../autogen_DeviceMotionEvent_rotationRate.ql | 2 +- .github/codeql/queries/autogen_Gyroscope.ql | 2 +- .github/codeql/queries/autogen_Gyroscope_x.ql | 2 +- .github/codeql/queries/autogen_Gyroscope_y.ql | 2 +- .github/codeql/queries/autogen_Gyroscope_z.ql | 2 +- .../autogen_Notification_permission.ql | 2 +- .../queries/autogen_OfflineAudioContext.ql | 2 +- .../queries/autogen_RTCPeerConnection.ql | 2 +- .../codeql/queries/autogen_SharedWorker.ql | 2 +- .../queries/autogen_navigator_appCodeName.ql | 2 +- .../autogen_navigator_cookieEnabled.ql | 16 ++++ .../queries/autogen_navigator_deviceMemory.ql | 2 +- .../queries/autogen_navigator_getBattery.ql | 2 +- .../queries/autogen_navigator_getGamepads.ql | 2 +- .../autogen_navigator_hardwareConcurrency.ql | 2 +- .../queries/autogen_navigator_keyboard.ql | 2 +- .../autogen_navigator_mediaCapabilities.ql | 2 +- .../queries/autogen_navigator_mediaDevices.ql | 2 +- .../queries/autogen_navigator_onLine.ql | 2 +- .../queries/autogen_navigator_permissions.ql | 2 +- .../queries/autogen_navigator_productSub.ql | 2 +- ...n_navigator_requestMediaKeySystemAccess.ql | 2 +- .../queries/autogen_navigator_storage.ql | 2 +- .../queries/autogen_navigator_vendorSub.ql | 2 +- .../queries/autogen_navigator_webdriver.ql | 2 +- ...togen_navigator_webkitPersistentStorage.ql | 2 +- ...utogen_navigator_webkitTemporaryStorage.ql | 2 +- .../queries/autogen_screen_availHeight.ql | 2 +- .../queries/autogen_screen_availLeft.ql | 2 +- .../codeql/queries/autogen_screen_availTop.ql | 2 +- .../queries/autogen_screen_availWidth.ql | 2 +- .../queries/autogen_screen_colorDepth.ql | 2 +- .../queries/autogen_screen_orientation.ql | 2 +- .../queries/autogen_screen_pixelDepth.ql | 2 +- ...2_RenderingContext_getContextAttributes.ql | 2 +- ...en_webgl2_RenderingContext_getExtension.ql | 2 +- ...en_webgl2_RenderingContext_getParameter.ql | 2 +- ...nderingContext_getShaderPrecisionFormat.ql | 2 +- ...RenderingContext_getSupportedExtensions.ql | 2 +- ...ogen_webgl2_RenderingContext_readPixels.ql | 2 +- ...l_RenderingContext_getContextAttributes.ql | 2 +- ...gen_webgl_RenderingContext_getExtension.ql | 2 +- ...gen_webgl_RenderingContext_getParameter.ql | 2 +- ...nderingContext_getShaderPrecisionFormat.ql | 2 +- ...RenderingContext_getSupportedExtensions.ql | 2 +- ...togen_webgl_RenderingContext_readPixels.ql | 2 +- .../autogen_window_devicePixelRatio.ql | 2 +- .../queries/autogen_window_indexedDB.ql | 2 +- .../queries/autogen_window_openDatabase.ql | 2 +- .../queries/autogen_window_outerHeight.ql | 2 +- .../queries/autogen_window_outerWidth.ql | 2 +- .../queries/autogen_window_screenLeft.ql | 2 +- .../queries/autogen_window_screenTop.ql | 2 +- .../codeql/queries/autogen_window_screenX.ql | 2 +- .../codeql/queries/autogen_window_screenY.ql | 2 +- .../gpt/x-domain/creative.html | 2 +- metadata/modules.json | 58 ++++++++++++- 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 | 18 ++++ 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 | 15 +++- 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/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/amxBidAdapter.json | 2 +- metadata/modules/amxIdSystem.json | 2 +- metadata/modules/aniviewBidAdapter.json | 2 +- metadata/modules/anonymisedRtdProvider.json | 20 ++++- metadata/modules/appStockSSPBidAdapter.json | 2 +- metadata/modules/appierBidAdapter.json | 2 +- metadata/modules/appnexusBidAdapter.json | 10 +-- metadata/modules/appushBidAdapter.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/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 | 44 +++++++++- 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/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 | 18 ++++ 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 | 11 ++- metadata/modules/liveIntentIdSystem.json | 2 +- metadata/modules/liveIntentRtdProvider.json | 2 +- metadata/modules/livewrappedBidAdapter.json | 2 +- metadata/modules/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.json | 2 +- metadata/modules/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 | 4 +- 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 | 18 ++++ 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/nubaBidAdapter.json | 13 +++ metadata/modules/oguryBidAdapter.json | 2 +- metadata/modules/omnidexBidAdapter.json | 82 ++++++++++++++++++- 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/performaxBidAdapter.json | 2 +- metadata/modules/pgamsspBidAdapter.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 | 44 +++++++++- 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/revcontentBidAdapter.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 | 36 ++++++++ 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/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 +- .../modules/uniquestWidgetBidAdapter.json | 13 +++ 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 | 10 +-- package.json | 2 +- 332 files changed, 725 insertions(+), 384 deletions(-) delete mode 100644 .github/codeql/queries/autogen_AudioWorkletNode.ql delete mode 100644 .github/codeql/queries/autogen_Date_getTimezoneOffset.ql create mode 100644 .github/codeql/queries/autogen_navigator_cookieEnabled.ql create mode 100644 metadata/modules/adbroBidAdapter.json create mode 100644 metadata/modules/empowerBidAdapter.json create mode 100644 metadata/modules/msftBidAdapter.json create mode 100644 metadata/modules/nubaBidAdapter.json create mode 100644 metadata/modules/scaliburBidAdapter.json create mode 100644 metadata/modules/uniquestWidgetBidAdapter.json diff --git a/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql b/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql index 9323058df37..48e6392d2ee 100644 --- a/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql +++ b/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("2d") and api = invocation.getAPropertyRead("getImageData") -select api, "getImageData is an indicator of fingerprinting, weighed 38.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getImageData is an indicator of fingerprinting, weighed 35.4 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql b/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql index 2318a0c8400..f7e181c124d 100644 --- a/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql +++ b/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("2d") and api = invocation.getAPropertyRead("isPointInPath") -select api, "isPointInPath is an indicator of fingerprinting, weighed 4216.23 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "isPointInPath is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql b/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql index b862cd21b51..e4c72a07434 100644 --- a/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql +++ b/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("2d") and api = invocation.getAPropertyRead("measureText") -select api, "measureText is an indicator of fingerprinting, weighed 48.2 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "measureText is an indicator of fingerprinting, weighed 44.88 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_AudioWorkletNode.ql b/.github/codeql/queries/autogen_AudioWorkletNode.ql deleted file mode 100644 index de9f13c2104..00000000000 --- a/.github/codeql/queries/autogen_AudioWorkletNode.ql +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @id prebid/audioworkletnode - * @name Use of AudioWorkletNode - * @kind problem - * @problem.severity warning - * @description Finds uses of AudioWorkletNode - */ - -// this file is autogenerated, see fingerprintApis.mjs - -import prebid -from SourceNode api -where - api = instantiationOf("AudioWorkletNode") -select api, "AudioWorkletNode is an indicator of fingerprinting, weighed 32.68 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Date_getTimezoneOffset.ql b/.github/codeql/queries/autogen_Date_getTimezoneOffset.ql deleted file mode 100644 index 43c4a8bcefa..00000000000 --- a/.github/codeql/queries/autogen_Date_getTimezoneOffset.ql +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @id prebid/date-gettimezoneoffset - * @name Access to Date.getTimezoneOffset - * @kind problem - * @problem.severity warning - * @description Finds uses of Date.getTimezoneOffset - */ - -// this file is autogenerated, see fingerprintApis.mjs - -import prebid -from SourceNode inst, SourceNode api -where - inst = instantiationOf("Date") and - api = inst.getAPropertyRead("getTimezoneOffset") -select api, "getTimezoneOffset is an indicator of fingerprinting, weighed 16.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql b/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql index 21a268b3f68..61504f043cb 100644 --- a/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql +++ b/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql @@ -12,4 +12,4 @@ import prebid from PropRef api where api.getPropertyName() = "acceleration" -select api, "acceleration is an indicator of fingerprinting, weighed 59.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "acceleration is an indicator of fingerprinting, weighed 57.38 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql b/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql index 1c970bca6ed..6be3f9ff0e5 100644 --- a/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql +++ b/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql @@ -12,4 +12,4 @@ import prebid from PropRef api where api.getPropertyName() = "accelerationIncludingGravity" -select api, "accelerationIncludingGravity is an indicator of fingerprinting, weighed 166.43 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "accelerationIncludingGravity is an indicator of fingerprinting, weighed 148.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql b/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql index 5256f3295fb..6d56771914c 100644 --- a/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql +++ b/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql @@ -12,4 +12,4 @@ import prebid from PropRef api where api.getPropertyName() = "rotationRate" -select api, "rotationRate is an indicator of fingerprinting, weighed 59.32 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "rotationRate is an indicator of fingerprinting, weighed 57.43 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope.ql b/.github/codeql/queries/autogen_Gyroscope.ql index d57c464e0eb..87a96e8aa94 100644 --- a/.github/codeql/queries/autogen_Gyroscope.ql +++ b/.github/codeql/queries/autogen_Gyroscope.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = instantiationOf("Gyroscope") -select api, "Gyroscope is an indicator of fingerprinting, weighed 189.7 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "Gyroscope is an indicator of fingerprinting, weighed 160.04 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope_x.ql b/.github/codeql/queries/autogen_Gyroscope_x.ql index 3344b446ddb..87fa3db54d5 100644 --- a/.github/codeql/queries/autogen_Gyroscope_x.ql +++ b/.github/codeql/queries/autogen_Gyroscope_x.ql @@ -13,4 +13,4 @@ from SourceNode inst, SourceNode api where inst = instantiationOf("Gyroscope") and api = inst.getAPropertyRead("x") -select api, "x is an indicator of fingerprinting, weighed 4216.23 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "x is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope_y.ql b/.github/codeql/queries/autogen_Gyroscope_y.ql index b5b75a1c5cd..7586e56ba4f 100644 --- a/.github/codeql/queries/autogen_Gyroscope_y.ql +++ b/.github/codeql/queries/autogen_Gyroscope_y.ql @@ -13,4 +13,4 @@ from SourceNode inst, SourceNode api where inst = instantiationOf("Gyroscope") and api = inst.getAPropertyRead("y") -select api, "y is an indicator of fingerprinting, weighed 4216.23 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "y is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope_z.ql b/.github/codeql/queries/autogen_Gyroscope_z.ql index 417556ad8ee..a772dc236d6 100644 --- a/.github/codeql/queries/autogen_Gyroscope_z.ql +++ b/.github/codeql/queries/autogen_Gyroscope_z.ql @@ -13,4 +13,4 @@ from SourceNode inst, SourceNode api where inst = instantiationOf("Gyroscope") and api = inst.getAPropertyRead("z") -select api, "z is an indicator of fingerprinting, weighed 4216.23 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "z is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Notification_permission.ql b/.github/codeql/queries/autogen_Notification_permission.ql index 555cb9b81af..ed009fae21b 100644 --- a/.github/codeql/queries/autogen_Notification_permission.ql +++ b/.github/codeql/queries/autogen_Notification_permission.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("Notification") and api = prop.getAPropertyRead("permission") -select api, "permission is an indicator of fingerprinting, weighed 21.19 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "permission is an indicator of fingerprinting, weighed 22.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_OfflineAudioContext.ql b/.github/codeql/queries/autogen_OfflineAudioContext.ql index c3406125a07..a567e8b6434 100644 --- a/.github/codeql/queries/autogen_OfflineAudioContext.ql +++ b/.github/codeql/queries/autogen_OfflineAudioContext.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = instantiationOf("OfflineAudioContext") -select api, "OfflineAudioContext is an indicator of fingerprinting, weighed 1062.57 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "OfflineAudioContext is an indicator of fingerprinting, weighed 1120.16 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_RTCPeerConnection.ql b/.github/codeql/queries/autogen_RTCPeerConnection.ql index 8d7e80174ee..ebbfa8f2e6f 100644 --- a/.github/codeql/queries/autogen_RTCPeerConnection.ql +++ b/.github/codeql/queries/autogen_RTCPeerConnection.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = instantiationOf("RTCPeerConnection") -select api, "RTCPeerConnection is an indicator of fingerprinting, weighed 47.04 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "RTCPeerConnection is an indicator of fingerprinting, weighed 47.69 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_SharedWorker.ql b/.github/codeql/queries/autogen_SharedWorker.ql index 1b2859b8523..34c9303f368 100644 --- a/.github/codeql/queries/autogen_SharedWorker.ql +++ b/.github/codeql/queries/autogen_SharedWorker.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = instantiationOf("SharedWorker") -select api, "SharedWorker is an indicator of fingerprinting, weighed 56.71 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "SharedWorker is an indicator of fingerprinting, weighed 93.37 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_appCodeName.ql b/.github/codeql/queries/autogen_navigator_appCodeName.ql index f6a5cd10bf3..fecacea0561 100644 --- a/.github/codeql/queries/autogen_navigator_appCodeName.ql +++ b/.github/codeql/queries/autogen_navigator_appCodeName.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("appCodeName") -select api, "appCodeName is an indicator of fingerprinting, weighed 127.81 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "appCodeName is an indicator of fingerprinting, weighed 145.05 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_cookieEnabled.ql b/.github/codeql/queries/autogen_navigator_cookieEnabled.ql new file mode 100644 index 00000000000..9d3938dd617 --- /dev/null +++ b/.github/codeql/queries/autogen_navigator_cookieEnabled.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/navigator-cookieenabled + * @name Access to navigator.cookieEnabled + * @kind problem + * @problem.severity warning + * @description Finds uses of navigator.cookieEnabled + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import prebid +from SourceNode prop, SourceNode api +where + prop = windowPropertyRead("navigator") and + api = prop.getAPropertyRead("cookieEnabled") +select api, "cookieEnabled is an indicator of fingerprinting, weighed 15.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_deviceMemory.ql b/.github/codeql/queries/autogen_navigator_deviceMemory.ql index 476a2927f12..48e46ee2319 100644 --- a/.github/codeql/queries/autogen_navigator_deviceMemory.ql +++ b/.github/codeql/queries/autogen_navigator_deviceMemory.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("deviceMemory") -select api, "deviceMemory is an indicator of fingerprinting, weighed 78.47 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "deviceMemory is an indicator of fingerprinting, weighed 70.5 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_getBattery.ql b/.github/codeql/queries/autogen_navigator_getBattery.ql index a039ebd2908..dff44f0f10a 100644 --- a/.github/codeql/queries/autogen_navigator_getBattery.ql +++ b/.github/codeql/queries/autogen_navigator_getBattery.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("getBattery") -select api, "getBattery is an indicator of fingerprinting, weighed 117.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getBattery is an indicator of fingerprinting, weighed 115.54 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_getGamepads.ql b/.github/codeql/queries/autogen_navigator_getGamepads.ql index 491b398a4fb..e9f436f2846 100644 --- a/.github/codeql/queries/autogen_navigator_getGamepads.ql +++ b/.github/codeql/queries/autogen_navigator_getGamepads.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("getGamepads") -select api, "getGamepads is an indicator of fingerprinting, weighed 256.1 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getGamepads is an indicator of fingerprinting, weighed 211.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql b/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql index dfb685511a2..21f70cbeaf3 100644 --- a/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql +++ b/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("hardwareConcurrency") -select api, "hardwareConcurrency is an indicator of fingerprinting, weighed 62.49 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "hardwareConcurrency is an indicator of fingerprinting, weighed 63.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_keyboard.ql b/.github/codeql/queries/autogen_navigator_keyboard.ql index 9a941c755f8..0083fa4d66c 100644 --- a/.github/codeql/queries/autogen_navigator_keyboard.ql +++ b/.github/codeql/queries/autogen_navigator_keyboard.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("keyboard") -select api, "keyboard is an indicator of fingerprinting, weighed 1296.7 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "keyboard is an indicator of fingerprinting, weighed 938.38 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql b/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql index 7547d6aa8d2..3b85315c4c4 100644 --- a/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql +++ b/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("mediaCapabilities") -select api, "mediaCapabilities is an indicator of fingerprinting, weighed 115.38 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "mediaCapabilities is an indicator of fingerprinting, weighed 123.84 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_mediaDevices.ql b/.github/codeql/queries/autogen_navigator_mediaDevices.ql index de8b5c470dc..ed7e930a68f 100644 --- a/.github/codeql/queries/autogen_navigator_mediaDevices.ql +++ b/.github/codeql/queries/autogen_navigator_mediaDevices.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("mediaDevices") -select api, "mediaDevices is an indicator of fingerprinting, weighed 110.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "mediaDevices is an indicator of fingerprinting, weighed 122.39 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_onLine.ql b/.github/codeql/queries/autogen_navigator_onLine.ql index 18061b2986d..5997eef8852 100644 --- a/.github/codeql/queries/autogen_navigator_onLine.ql +++ b/.github/codeql/queries/autogen_navigator_onLine.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("onLine") -select api, "onLine is an indicator of fingerprinting, weighed 19.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "onLine is an indicator of fingerprinting, weighed 20.21 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_permissions.ql b/.github/codeql/queries/autogen_navigator_permissions.ql index dcc0c2930c2..55fb1eb60d5 100644 --- a/.github/codeql/queries/autogen_navigator_permissions.ql +++ b/.github/codeql/queries/autogen_navigator_permissions.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("permissions") -select api, "permissions is an indicator of fingerprinting, weighed 72.06 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "permissions is an indicator of fingerprinting, weighed 83.98 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_productSub.ql b/.github/codeql/queries/autogen_navigator_productSub.ql index ca28abef4cf..e323428f028 100644 --- a/.github/codeql/queries/autogen_navigator_productSub.ql +++ b/.github/codeql/queries/autogen_navigator_productSub.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("productSub") -select api, "productSub is an indicator of fingerprinting, weighed 475.08 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "productSub is an indicator of fingerprinting, weighed 483.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql b/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql index 3d99aae1178..c7101c81361 100644 --- a/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql +++ b/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("requestMediaKeySystemAccess") -select api, "requestMediaKeySystemAccess is an indicator of fingerprinting, weighed 15.3 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "requestMediaKeySystemAccess is an indicator of fingerprinting, weighed 16.48 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_storage.ql b/.github/codeql/queries/autogen_navigator_storage.ql index 5c96fc5350a..b73d9231393 100644 --- a/.github/codeql/queries/autogen_navigator_storage.ql +++ b/.github/codeql/queries/autogen_navigator_storage.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("storage") -select api, "storage is an indicator of fingerprinting, weighed 143.61 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "storage is an indicator of fingerprinting, weighed 150.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_vendorSub.ql b/.github/codeql/queries/autogen_navigator_vendorSub.ql index cca8739f8df..64d834523a8 100644 --- a/.github/codeql/queries/autogen_navigator_vendorSub.ql +++ b/.github/codeql/queries/autogen_navigator_vendorSub.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("vendorSub") -select api, "vendorSub is an indicator of fingerprinting, weighed 1543.86 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "vendorSub is an indicator of fingerprinting, weighed 1670.4 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_webdriver.ql b/.github/codeql/queries/autogen_navigator_webdriver.ql index c6e5f5affca..80684d09387 100644 --- a/.github/codeql/queries/autogen_navigator_webdriver.ql +++ b/.github/codeql/queries/autogen_navigator_webdriver.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("webdriver") -select api, "webdriver is an indicator of fingerprinting, weighed 32.18 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "webdriver is an indicator of fingerprinting, weighed 32.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql b/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql index 646c73b83bb..5938592ba38 100644 --- a/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql +++ b/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("webkitPersistentStorage") -select api, "webkitPersistentStorage is an indicator of fingerprinting, weighed 132.06 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "webkitPersistentStorage is an indicator of fingerprinting, weighed 129.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql b/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql index 1dee42c327f..7d3c990b5fe 100644 --- a/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql +++ b/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("webkitTemporaryStorage") -select api, "webkitTemporaryStorage is an indicator of fingerprinting, weighed 42.05 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "webkitTemporaryStorage is an indicator of fingerprinting, weighed 39.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availHeight.ql b/.github/codeql/queries/autogen_screen_availHeight.ql index e0ca9acf366..a589b2867bd 100644 --- a/.github/codeql/queries/autogen_screen_availHeight.ql +++ b/.github/codeql/queries/autogen_screen_availHeight.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availHeight") -select api, "availHeight is an indicator of fingerprinting, weighed 72.8 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availHeight is an indicator of fingerprinting, weighed 72.86 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availLeft.ql b/.github/codeql/queries/autogen_screen_availLeft.ql index f8cafbee083..5b258b3e4bc 100644 --- a/.github/codeql/queries/autogen_screen_availLeft.ql +++ b/.github/codeql/queries/autogen_screen_availLeft.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availLeft") -select api, "availLeft is an indicator of fingerprinting, weighed 665.2 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availLeft is an indicator of fingerprinting, weighed 617.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availTop.ql b/.github/codeql/queries/autogen_screen_availTop.ql index 8fb1cfa15b0..8487c3ba39b 100644 --- a/.github/codeql/queries/autogen_screen_availTop.ql +++ b/.github/codeql/queries/autogen_screen_availTop.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availTop") -select api, "availTop is an indicator of fingerprinting, weighed 1527.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availTop is an indicator of fingerprinting, weighed 1374.93 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availWidth.ql b/.github/codeql/queries/autogen_screen_availWidth.ql index 33445cafaf3..6e109fd9abc 100644 --- a/.github/codeql/queries/autogen_screen_availWidth.ql +++ b/.github/codeql/queries/autogen_screen_availWidth.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availWidth") -select api, "availWidth is an indicator of fingerprinting, weighed 66.83 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availWidth is an indicator of fingerprinting, weighed 67.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_colorDepth.ql b/.github/codeql/queries/autogen_screen_colorDepth.ql index d60e95e1fbb..577fcdc7a3f 100644 --- a/.github/codeql/queries/autogen_screen_colorDepth.ql +++ b/.github/codeql/queries/autogen_screen_colorDepth.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("colorDepth") -select api, "colorDepth is an indicator of fingerprinting, weighed 33.93 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "colorDepth is an indicator of fingerprinting, weighed 34.52 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_orientation.ql b/.github/codeql/queries/autogen_screen_orientation.ql index 4dff29ca88c..0e4e800f600 100644 --- a/.github/codeql/queries/autogen_screen_orientation.ql +++ b/.github/codeql/queries/autogen_screen_orientation.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("orientation") -select api, "orientation is an indicator of fingerprinting, weighed 43.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "orientation is an indicator of fingerprinting, weighed 44.76 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_pixelDepth.ql b/.github/codeql/queries/autogen_screen_pixelDepth.ql index 8848e614961..d5444006a60 100644 --- a/.github/codeql/queries/autogen_screen_pixelDepth.ql +++ b/.github/codeql/queries/autogen_screen_pixelDepth.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("pixelDepth") -select api, "pixelDepth is an indicator of fingerprinting, weighed 34.57 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "pixelDepth is an indicator of fingerprinting, weighed 37.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql index 907d2af6b63..98f04b45434 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getContextAttributes") -select api, "getContextAttributes is an indicator of fingerprinting, weighed 175.29 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getContextAttributes is an indicator of fingerprinting, weighed 187.76 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql index 97deebacbf8..f36b5e2ea77 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getExtension") -select api, "getExtension is an indicator of fingerprinting, weighed 44.76 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getExtension is an indicator of fingerprinting, weighed 44.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql index 77a25606be0..3eea9979ed2 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getParameter") -select api, "getParameter is an indicator of fingerprinting, weighed 42.17 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getParameter is an indicator of fingerprinting, weighed 41.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql index f2b0de4d528..38f8ff15608 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getShaderPrecisionFormat") -select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 105.96 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 103.05 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql index 5039c59294a..240d70f969e 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getSupportedExtensions") -select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 495.53 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 466.18 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql index 1b89fb6b857..04856b3d38a 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("readPixels") -select api, "readPixels is an indicator of fingerprinting, weighed 61.17 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "readPixels is an indicator of fingerprinting, weighed 66.74 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql index 1ad0af71669..f0fdd734b1a 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getContextAttributes") -select api, "getContextAttributes is an indicator of fingerprinting, weighed 2131.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getContextAttributes is an indicator of fingerprinting, weighed 2006.6 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql index 87922396ad0..d9d840d4ad8 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getExtension") -select api, "getExtension is an indicator of fingerprinting, weighed 19.17 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getExtension is an indicator of fingerprinting, weighed 21.18 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql index 2a5e408e668..a86709d09d5 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getParameter") -select api, "getParameter is an indicator of fingerprinting, weighed 21.25 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getParameter is an indicator of fingerprinting, weighed 23.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql index ac6972b348e..5d51a714c60 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getShaderPrecisionFormat") -select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 750.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 688.97 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql index 6c6cf48555d..d8cb422f67e 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getSupportedExtensions") -select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 1058.69 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 1008.67 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql index efd2e9515ae..efdd39edf6d 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("readPixels") -select api, "readPixels is an indicator of fingerprinting, weighed 20.37 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "readPixels is an indicator of fingerprinting, weighed 15.25 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_devicePixelRatio.ql b/.github/codeql/queries/autogen_window_devicePixelRatio.ql index 7d6cca21576..e4cf78b5ecb 100644 --- a/.github/codeql/queries/autogen_window_devicePixelRatio.ql +++ b/.github/codeql/queries/autogen_window_devicePixelRatio.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("devicePixelRatio") -select api, "devicePixelRatio is an indicator of fingerprinting, weighed 18.68 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "devicePixelRatio is an indicator of fingerprinting, weighed 19.19 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_indexedDB.ql b/.github/codeql/queries/autogen_window_indexedDB.ql index 4ce36d5a518..db33f272305 100644 --- a/.github/codeql/queries/autogen_window_indexedDB.ql +++ b/.github/codeql/queries/autogen_window_indexedDB.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("indexedDB") -select api, "indexedDB is an indicator of fingerprinting, weighed 18.13 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "indexedDB is an indicator of fingerprinting, weighed 17.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_openDatabase.ql b/.github/codeql/queries/autogen_window_openDatabase.ql index d90f5b491c0..a6f69d6f020 100644 --- a/.github/codeql/queries/autogen_window_openDatabase.ql +++ b/.github/codeql/queries/autogen_window_openDatabase.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("openDatabase") -select api, "openDatabase is an indicator of fingerprinting, weighed 139.85 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "openDatabase is an indicator of fingerprinting, weighed 137.87 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_outerHeight.ql b/.github/codeql/queries/autogen_window_outerHeight.ql index 4c4b64e9439..16acef61f0a 100644 --- a/.github/codeql/queries/autogen_window_outerHeight.ql +++ b/.github/codeql/queries/autogen_window_outerHeight.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("outerHeight") -select api, "outerHeight is an indicator of fingerprinting, weighed 200.62 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "outerHeight is an indicator of fingerprinting, weighed 181.21 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_outerWidth.ql b/.github/codeql/queries/autogen_window_outerWidth.ql index 4313c7cc937..af08cf7af41 100644 --- a/.github/codeql/queries/autogen_window_outerWidth.ql +++ b/.github/codeql/queries/autogen_window_outerWidth.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("outerWidth") -select api, "outerWidth is an indicator of fingerprinting, weighed 107.23 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "outerWidth is an indicator of fingerprinting, weighed 105.02 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenLeft.ql b/.github/codeql/queries/autogen_window_screenLeft.ql index 0e39d072655..e3d8ac11c01 100644 --- a/.github/codeql/queries/autogen_window_screenLeft.ql +++ b/.github/codeql/queries/autogen_window_screenLeft.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenLeft") -select api, "screenLeft is an indicator of fingerprinting, weighed 286.83 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenLeft is an indicator of fingerprinting, weighed 332.08 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenTop.ql b/.github/codeql/queries/autogen_window_screenTop.ql index 517da52b3bf..5c13951385e 100644 --- a/.github/codeql/queries/autogen_window_screenTop.ql +++ b/.github/codeql/queries/autogen_window_screenTop.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenTop") -select api, "screenTop is an indicator of fingerprinting, weighed 286.72 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenTop is an indicator of fingerprinting, weighed 329.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenX.ql b/.github/codeql/queries/autogen_window_screenX.ql index 24e4702696b..cf5d788ce45 100644 --- a/.github/codeql/queries/autogen_window_screenX.ql +++ b/.github/codeql/queries/autogen_window_screenX.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenX") -select api, "screenX is an indicator of fingerprinting, weighed 337.97 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenX is an indicator of fingerprinting, weighed 301.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenY.ql b/.github/codeql/queries/autogen_window_screenY.ql index 23675d4d8d6..aecf595a15b 100644 --- a/.github/codeql/queries/autogen_window_screenY.ql +++ b/.github/codeql/queries/autogen_window_screenY.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenY") -select api, "screenY is an indicator of fingerprinting, weighed 324.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenY is an indicator of fingerprinting, weighed 285.97 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index ae8456c19e0..967147b34ba 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' - + - - + + + + + +

Basic Prebid.js Example using Neuwo Rtd Provider

+ +
+ Looks like you're not following the testing environment setup, try accessing + + http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html + + after running commands in the prebid.js source folder that includes libraries/modules/neuwoRtdProvider.js + + npm ci + npx gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter + + // No tests + npx gulp serve-fast --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --notests + + // Only tests + npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/neuwoRtdProvider_spec.js + +
+ +
+

Neuwo Rtd Provider Configuration

+

Add token and url to use for Neuwo extension configuration

+ + + + + +
+ +
+

Ad Examples

- googletag.cmd.push(function() { - googletag.defineSlot('/19968336/header-bid-tag-0', 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.pubads().enableSingleRequest(); - googletag.enableServices(); - }); - - - -

Basic Prebid.js Example using neuwoRtdProvider

-
- Looks like you're not following the testing environment setup, try accessing http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html - after running commands in the prebid.js source folder that includes libraries/modules/neuwoRtdProvider.js - - npm ci - npm i -g gulp-cli - gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter - -
-

Add token and url to use for Neuwo extension configuration

- - - - +

Div-1

+
+ + Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click + "Update" and there are no errors. +
-
Div-1
-
- Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click "Update" +
+

Div-2

+
+ + Ad spot div-2: This content will be replaced by prebid.js and/or related components once you click + "Update" and there are no errors. +
+
-
+
+

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 + INFO: NeuwoRTDModule injectIabCategories: post-injection bidsConfig +

+
-
Div-2
-
- Ad spot div-2: Replaces this text as well, if everything goes to plan - - -
+ + + - - \ No newline at end of file diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 4e625163eca..99715f0b484 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -1,173 +1,183 @@ -import { deepAccess, deepSetValue, generateUUID, logError, logInfo, mergeDeep } from '../src/utils.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; - -export const DATA_PROVIDER = 'neuwo.ai'; -const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 -const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' -const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' +/** + * @module neuwoRtdProvider + * @author Grzegorz Malisz + * @see {project-root-directory}/integrationExamples/gpt/neuwoRtdProvider_example.html for an example/testing page. + * @see {project-root-directory}/test/spec/modules/neuwoRtdProvider_spec.js for unit tests. + * @description + * This module is a Prebid.js Real-Time Data (RTD) provider that integrates with the Neuwo API. + * + * It fetches contextual marketing categories (IAB content and audience) for the current page from the Neuwo API. + * The retrieved data is then injected into the bid request as OpenRTB (ORTB2)`site.content.data` + * and `user.data` fragments, making it available for bidders to use in their decisioning process. + * + * @see {@link https://docs.prebid.org/dev-docs/add-rtd-submodule.html} for more information on development of Prebid.js RTD modules. + * @see {@link https://docs.prebid.org/features/firstPartyData.html} for more information on Prebid.js First Party Data. + * @see {@link https://www.neuwo.ai/} for more information on the Neuwo API. + */ +import { ajax } from "../src/ajax.js"; +import { submodule } from "../src/hook.js"; +import { getRefererInfo } from "../src/refererDetection.js"; +import { deepSetValue, logError, logInfo, mergeDeep } from "../src/utils.js"; + +const MODULE_NAME = "NeuwoRTDModule"; +export const DATA_PROVIDER = "www.neuwo.ai"; + +// Maps the IAB Content Taxonomy version string to the corresponding segtax ID. +// Based on https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/main/AdCOM%20v1.0%20FINAL.md#list--category-taxonomies- +const IAB_CONTENT_TAXONOMY_MAP = { + "1.0": 1, + "2.0": 2, + "2.1": 5, + "2.2": 6, + "3.0": 7, + "3.1": 9, +}; + +/** + * Validates the configuration and initialises the module. + * @param {Object} config The module configuration. + * @param {Object} userConsent The user consent object. + * @returns {boolean} `true` if the module is configured correctly, otherwise `false`. + */ function init(config, userConsent) { - // config.params = config.params || {} - // ignore module if publicToken is missing (module setup failure) - if (!config || !config.params || !config.params.publicToken) { - logError('publicToken missing', 'NeuwoRTDModule', 'config.params.publicToken') + const params = config?.params || {}; + if (!params.neuwoApiUrl) { + logError(MODULE_NAME, "init:", "Missing Neuwo Edge API Endpoint URL"); return false; } - if (!config || !config.params || !config.params.apiUrl) { - logError('apiUrl missing', 'NeuwoRTDModule', 'config.params.apiUrl') + if (!params.neuwoApiToken) { + logError(MODULE_NAME, "init:", "Missing Neuwo API Token missing"); return false; } return true; } +/** + * Fetches contextual data from the Neuwo API and enriches the bid request object with IAB categories. + * @param {Object} reqBidsConfigObj The bid request configuration object. + * @param {function} callback The callback function to continue the auction. + * @param {Object} config The module configuration. + * @param {Object} userConsent The user consent object. + */ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - const confParams = config.params || {}; - logInfo('NeuwoRTDModule', 'starting getBidRequestData') - - const wrappedArgUrl = encodeURIComponent(confParams.argUrl || getRefererInfo().page); - /* adjust for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') */ - const joiner = confParams.apiUrl.indexOf('?') < 0 ? '?' : '&' - const url = confParams.apiUrl + joiner + [ - 'token=' + confParams.publicToken, - 'url=' + wrappedArgUrl - ].join('&') - const billingId = generateUUID(); - - const success = (responseContent) => { - logInfo('NeuwoRTDModule', 'GetAiTopics: response', responseContent) + logInfo(MODULE_NAME, "getBidRequestData:", "starting getBidRequestData", config); + + const { websiteToAnalyseUrl, neuwoApiUrl, neuwoApiToken, iabContentTaxonomyVersion } = + config.params; + + const pageUrl = encodeURIComponent(websiteToAnalyseUrl || getRefererInfo().page); + // Adjusted for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') + const joiner = neuwoApiUrl.indexOf("?") < 0 ? "?" : "&"; + const neuwoApiUrlFull = + neuwoApiUrl + joiner + ["token=" + neuwoApiToken, "url=" + pageUrl].join("&"); + + const success = (response) => { + logInfo(MODULE_NAME, "getBidRequestData:", "Neuwo API raw response:", response); try { - const jsonContent = JSON.parse(responseContent); - if (jsonContent.marketing_categories) { - events.emit(EVENTS.BILLABLE_EVENT, { type: 'request', billingId, vendor: neuwoRtdModule.name }) - } - injectTopics(jsonContent, reqBidsConfigObj, billingId) + const responseJson = JSON.parse(response); + injectIabCategories(responseJson, reqBidsConfigObj, iabContentTaxonomyVersion); } catch (ex) { - logError('NeuwoRTDModule', 'Response to JSON parse error', ex) + logError(MODULE_NAME, "getBidRequestData:", "Error while processing Neuwo API response", ex); } - callback() - } + callback(); + }; const error = (err) => { - logError('xhr error', null, err); - callback() - } + logError(MODULE_NAME, "getBidRequestData:", "AJAX error:", err); + callback(); + }; - ajax(url, {success, error}, null, { - // could assume Origin header is set, or - // customHeaders: { 'Origin': 'Origin' } - }) + ajax(neuwoApiUrlFull, { success, error }, null); } -export function addFragment(base, path, addition) { - const container = {} - deepSetValue(container, path, addition) - mergeDeep(base, container) -} +// +// HELPER FUNCTIONS +// /** - * Concatenate a base array and an array within an object - * non-array bases will be arrays, non-arrays at object key will be discarded - * @param {Array} base base array to add to - * @param {object} source object to get an array from - * @param {string} key dot-notated path to array within object - * @returns base + source[key] if that's an array + * Injects data into the OpenRTB 2.x global fragments of the bid request object. + * @param {Object} reqBidsConfigObj The main bid request configuration object. + * @param {string} path The dot-notation path where the data should be injected (e.g., 'site.content.data'). + * @param {*} data The data to inject at the specified path. */ -function combineArray(base, source, key) { - if (Array.isArray(base) === false) base = [] - const addition = deepAccess(source, key, []) - if (Array.isArray(addition)) return base.concat(addition) - else return base +export function injectOrtbData(reqBidsConfigObj, path, data) { + const container = {}; + deepSetValue(container, path, data); + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, container); } -export function injectTopics(topics, bidsConfig) { - topics = topics || {} +/** + * Builds an IAB category data object for use in OpenRTB. + * @param {Object} marketingCategories Marketing Categories returned by Neuwo API. + * @param {string[]} tiers The tier keys to extract from marketingCategories. + * @param {number} segtax The IAB taxonomy version Id. + * @returns {Object} The constructed data object. + */ +export function buildIabData(marketingCategories, tiers, segtax) { + const data = { + name: DATA_PROVIDER, + segment: [], + ext: { segtax }, + }; + + tiers.forEach((tier) => { + const tierData = marketingCategories?.[tier]; + if (Array.isArray(tierData)) { + tierData.forEach((item) => { + const ID = item?.ID; + const label = item?.label; + + if (ID && label) { + data.segment.push({ id: ID, name: label }); + } + }); + } + }); - // join arrays of IAB category details to single array - const combinedTiers = combineArray( - combineArray([], topics, RESPONSE_IAB_TIER_1), - topics, RESPONSE_IAB_TIER_2) + return data; +} - const segment = pickSegments(combinedTiers) - // effectively gets topics.marketing_categories.iab_tier_1, topics.marketing_categories.iab_tier_2 - // used as FPD segments content +/** + * Processes the Neuwo API response to build and inject IAB content and audience categories + * into the bid request object. + * @param {Object} responseJson The parsed JSON response from the Neuwo API. + * @param {Object} reqBidsConfigObj The bid request configuration object to be modified. + * @param {string} iabContentTaxonomyVersion The version of the IAB content taxonomy to use for segtax mapping. + */ +function injectIabCategories(responseJson, reqBidsConfigObj, iabContentTaxonomyVersion) { + const marketingCategories = responseJson.marketing_categories; - const IABSegments = { - name: DATA_PROVIDER, - ext: { segtax: SEGTAX_IAB }, - segment + if (!marketingCategories) { + logError(MODULE_NAME, "injectIabCategories:", "No Marketing Categories in Neuwo API response."); + return } - addFragment(bidsConfig.ortb2Fragments.global, 'site.content.data', [IABSegments]) + // Process content categories + const contentTiers = ["iab_tier_1", "iab_tier_2", "iab_tier_3"]; + const contentData = buildIabData( + marketingCategories, + contentTiers, + IAB_CONTENT_TAXONOMY_MAP[iabContentTaxonomyVersion] || IAB_CONTENT_TAXONOMY_MAP["3.0"] + ); - // upgrade category taxonomy to IAB 2.2, inject result to page categories - if (segment.length > 0) { - addFragment(bidsConfig.ortb2Fragments.global, 'site.pagecat', segment.map(s => s.id)) - } + // Process audience categories + const audienceTiers = ["iab_audience_tier_3", "iab_audience_tier_4", "iab_audience_tier_5"]; + const audienceData = buildIabData(marketingCategories, audienceTiers, 4); - logInfo('NeuwoRTDModule', 'injectTopics: post-injection bidsConfig', bidsConfig) -} + logInfo(MODULE_NAME, "injectIabCategories:", "contentData structure:", contentData); + logInfo(MODULE_NAME, "injectIabCategories:", "audienceData structure:", audienceData); -const D_IAB_ID = { // Content Taxonomy version 2.0 final release November 2017 [sic] (Taxonomy ID Mapping, IAB versions 2.0 - 2.2) - 'IAB19-1': '603', 'IAB6-1': '193', 'IAB5-2': '133', 'IAB20-1': '665', 'IAB20-2': '656', 'IAB23-2': '454', 'IAB3-2': '102', 'IAB20-3': '672', 'IAB8-5': '211', - 'IAB8-18': '211', 'IAB7-4': '288', 'IAB7-5': '233', 'IAB17-12': '484', 'IAB19-3': '608', 'IAB21-1': '442', 'IAB9-2': '248', 'IAB15-1': '456', 'IAB9-17': '265', 'IAB20-4': '658', - 'IAB2-3': '30', 'IAB2-1': '32', 'IAB17-1': '518', 'IAB2-2': '34', 'IAB2': '1', 'IAB8-2': '215', 'IAB17-2': '545', 'IAB17-26': '547', 'IAB9-3': '249', 'IAB18-1': '553', 'IAB20-5': '674', - 'IAB15-2': '465', 'IAB3-3': '119', 'IAB16-2': '423', 'IAB9-4': '259', 'IAB9-5': '270', 'IAB18-2': '574', 'IAB17-4': '549', 'IAB7-33': '312', 'IAB1-1': '42', 'IAB17-5': '485', 'IAB23-3': '458', - 'IAB20-6': '675', 'IAB3': '53', 'IAB20-7': '676', 'IAB19-5': '633', 'IAB20-9': '677', 'IAB9-6': '250', 'IAB17-6': '499', 'IAB2-4': '25', 'IAB9-7': '271', 'IAB4-11': '125', 'IAB4-1': '126', - 'IAB4': '123', 'IAB16-3': '424', 'IAB2-5': '18', 'IAB17-7': '486', 'IAB15-3': '466', 'IAB23-5': '459', 'IAB9-9': '260', 'IAB2-22': '19', 'IAB17-8': '500', 'IAB9-10': '261', 'IAB5-5': '137', - 'IAB9-11': '262', 'IAB2-21': '3', 'IAB19-2': '610', 'IAB19-8': '600', 'IAB19-9': '601', 'IAB3-5': '121', 'IAB9-15': '264', 'IAB2-6': '8', 'IAB2-7': '9', 'IAB22-2': '474', 'IAB17-9': '491', - 'IAB2-8': '10', 'IAB20-12': '678', 'IAB17-3': '492', 'IAB19-12': '611', 'IAB14-1': '188', 'IAB6-3': '194', 'IAB7-17': '316', 'IAB19-13': '612', 'IAB8-8': '217', 'IAB9-1': '205', 'IAB19-22': '613', - 'IAB8-9': '218', 'IAB14-2': '189', 'IAB16-4': '425', 'IAB9-12': '251', 'IAB5': '132', 'IAB6-9': '190', 'IAB19-15': '623', 'IAB17-17': '496', 'IAB20-14': '659', 'IAB6': '186', 'IAB20-26': '666', - 'IAB17-10': '510', 'IAB13-4': '396', 'IAB1-3': '201', 'IAB16-1': '426', 'IAB17-11': '511', 'IAB17-13': '511', 'IAB17-32': '511', 'IAB7-1': '225', 'IAB8': '210', 'IAB8-10': '219', 'IAB9-13': '266', - 'IAB10-4': '275', 'IAB9-14': '273', 'IAB15-8': '469', 'IAB15-4': '470', 'IAB17-15': '512', 'IAB3-7': '77', 'IAB19-16': '614', 'IAB3-8': '78', 'IAB2-10': '22', 'IAB2-12': '22', 'IAB2-11': '11', - 'IAB8-12': '221', 'IAB7-35': '223', 'IAB7-38': '223', 'IAB7-24': '296', 'IAB13-5': '411', 'IAB7-25': '234', 'IAB23-6': '460', 'IAB9': '239', 'IAB7-26': '235', 'IAB10': '274', 'IAB10-1': '278', - 'IAB10-2': '279', 'IAB19-17': '634', 'IAB10-5': '280', 'IAB5-10': '145', 'IAB5-11': '146', 'IAB20-17': '667', 'IAB17-16': '497', 'IAB20-18': '668', 'IAB3-9': '55', 'IAB1-4': '440', 'IAB17-18': '514', - 'IAB17-27': '515', 'IAB10-3': '282', 'IAB19-25': '618', 'IAB17-19': '516', 'IAB13-6': '398', 'IAB10-7': '283', 'IAB12-1': '382', 'IAB19-24': '624', 'IAB6-4': '195', 'IAB23-7': '461', 'IAB9-19': '252', - 'IAB4-4': '128', 'IAB4-5': '127', 'IAB23-8': '462', 'IAB10-8': '284', 'IAB5-8': '147', 'IAB16-5': '427', 'IAB11-2': '383', 'IAB12-3': '384', 'IAB3-10': '57', 'IAB2-13': '23', 'IAB9-20': '241', - 'IAB3-1': '58', 'IAB3-11': '58', 'IAB14-4': '191', 'IAB17-20': '520', 'IAB7-31': '228', 'IAB7-37': '301', 'IAB3-12': '107', 'IAB2-14': '13', 'IAB17-25': '519', 'IAB2-15': '27', 'IAB1-5': '324', - 'IAB1-6': '338', 'IAB9-16': '243', 'IAB13-8': '412', 'IAB12-2': '385', 'IAB9-21': '253', 'IAB8-6': '222', 'IAB7-32': '229', 'IAB2-16': '14', 'IAB17-23': '521', 'IAB13-9': '413', 'IAB17-24': '501', - 'IAB9-22': '254', 'IAB15-5': '244', 'IAB6-2': '196', 'IAB6-5': '197', 'IAB6-6': '198', 'IAB2-17': '24', 'IAB13-2': '405', 'IAB13': '391', 'IAB13-7': '410', 'IAB13-12': '415', 'IAB16': '422', - 'IAB9-23': '255', 'IAB7-36': '236', 'IAB15-6': '471', 'IAB2-18': '15', 'IAB11-4': '386', 'IAB1-2': '432', 'IAB5-9': '139', 'IAB6-7': '305', 'IAB5-12': '149', 'IAB5-13': '134', 'IAB19-4': '631', - 'IAB19-19': '631', 'IAB19-20': '631', 'IAB19-32': '631', 'IAB9-24': '245', 'IAB21': '441', 'IAB21-3': '451', 'IAB23': '453', 'IAB10-9': '276', 'IAB4-9': '130', 'IAB16-6': '429', 'IAB4-6': '129', - 'IAB13-10': '416', 'IAB2-19': '28', 'IAB17-28': '525', 'IAB9-25': '272', 'IAB17-29': '527', 'IAB17-30': '227', 'IAB17-31': '530', 'IAB22-1': '481', 'IAB15': '464', 'IAB9-26': '246', 'IAB9-27': '256', - 'IAB9-28': '267', 'IAB17-33': '502', 'IAB19-35': '627', 'IAB2-20': '4', 'IAB7-39': '307', 'IAB19-30': '605', 'IAB22': '473', 'IAB17-34': '503', 'IAB17-35': '531', 'IAB7-19': '309', 'IAB7-40': '310', - 'IAB19-6': '635', 'IAB7-41': '237', 'IAB17-36': '504', 'IAB17-44': '533', 'IAB20-23': '662', 'IAB15-7': '472', 'IAB20-24': '671', 'IAB5-14': '136', 'IAB6-8': '199', 'IAB17': '483', 'IAB9-29': '263', - 'IAB2-23': '5', 'IAB13-11': '414', 'IAB4-3': '395', 'IAB18': '552', 'IAB7-42': '311', 'IAB17-37': '505', 'IAB17-38': '537', 'IAB17-39': '538', 'IAB19-26': '636', 'IAB19': '596', 'IAB1-7': '640', - 'IAB17-40': '539', 'IAB7-43': '293', 'IAB20': '653', 'IAB8-16': '212', 'IAB8-17': '213', 'IAB16-7': '430', 'IAB9-30': '680', 'IAB17-41': '541', 'IAB17-42': '542', 'IAB17-43': '506', 'IAB15-10': '390', - 'IAB19-23': '607', 'IAB19-34': '629', 'IAB14-7': '165', 'IAB7-44': '231', 'IAB7-45': '238', 'IAB9-31': '257', 'IAB5-1': '135', 'IAB7-2': '301', 'IAB18-6': '580', 'IAB7-3': '297', 'IAB23-1': '453', - 'IAB8-1': '214', 'IAB7-6': '312', 'IAB7-7': '300', 'IAB7-8': '301', 'IAB13-1': '410', 'IAB7-9': '301', 'IAB15-9': '465', 'IAB7-10': '313', 'IAB3-4': '602', 'IAB20-8': '660', 'IAB8-3': '214', - 'IAB20-10': '660', 'IAB7-11': '314', 'IAB20-11': '660', 'IAB23-4': '459', 'IAB9-8': '270', 'IAB8-4': '214', 'IAB7-12': '306', 'IAB7-13': '313', 'IAB7-14': '287', 'IAB18-5': '575', 'IAB7-15': '315', - 'IAB8-7': '214', 'IAB19-11': '616', 'IAB7-16': '289', 'IAB7-18': '301', 'IAB7-20': '290', 'IAB20-13': '659', 'IAB7-21': '313', 'IAB18-3': '579', 'IAB13-3': '52', 'IAB20-15': '659', 'IAB8-11': '214', - 'IAB7-22': '318', 'IAB20-16': '659', 'IAB7-23': '313', 'IAB7': '223', 'IAB10-6': '634', 'IAB7-27': '318', 'IAB11-1': '388', 'IAB7-29': '318', 'IAB7-30': '304', 'IAB19-18': '619', 'IAB8-13': '214', - 'IAB20-19': '659', 'IAB20-20': '657', 'IAB8-14': '214', 'IAB18-4': '565', 'IAB23-9': '459', 'IAB11': '379', 'IAB8-15': '214', 'IAB20-21': '662', 'IAB17-21': '492', 'IAB17-22': '518', 'IAB12': '379', - 'IAB23-10': '453', 'IAB7-34': '301', 'IAB4-8': '395', 'IAB26-3': '608', 'IAB20-25': '151', 'IAB20-27': '659' -} + injectOrtbData(reqBidsConfigObj, "site.content.data", [contentData]); + injectOrtbData(reqBidsConfigObj, "user.data", [audienceData]); -export function convertSegment(segment) { - if (!segment) return {} - return { - id: D_IAB_ID[segment.id || segment.ID] - } -} - -/** - * map array of objects to segments - * @param {Array<{ID: string}>} normalizable - * @returns array of IAB "segments" - */ -export function pickSegments(normalizable) { - if (Array.isArray(normalizable) === false) return [] - return normalizable.map(convertSegment) - .filter(t => t.id) + logInfo(MODULE_NAME, "injectIabCategories:", "post-injection bidsConfig", reqBidsConfigObj); } export const neuwoRtdModule = { - name: 'NeuwoRTDModule', + name: MODULE_NAME, init, - getBidRequestData -} + getBidRequestData, +}; -submodule('realTimeData', neuwoRtdModule) +submodule("realTimeData", neuwoRtdModule); diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md index fb52734d451..acd3f27d3ff 100644 --- a/modules/neuwoRtdProvider.md +++ b/modules/neuwoRtdProvider.md @@ -1,51 +1,129 @@ # Overview -Module Name: Neuwo Rtd Provider -Module Type: Rtd Provider -Maintainer: neuwo.ai + Module Name: Neuwo Rtd Provider + Module Type: Rtd Provider + Maintainer: Grzegorz Malisz (grzegorz.malisz@neuwo.ai) -# Description +## Description -The Neuwo AI RTD module is an advanced AI solution for real-time data processing in the field of contextual targeting and advertising. With its cutting-edge algorithms, it allows advertisers to target their audiences with the highest level of precision based on context, while also delivering a seamless user experience. +The Neuwo RTD provider fetches real-time contextual data from the Neuwo API. When installed, the module retrieves IAB content and audience categories relevant to the current page's content. -The module provides advertiser with valuable insights and real-time contextual bidding capabilities. Whether you're a seasoned advertising professional or just starting out, Neuwo AI RTD module is the ultimate tool for contextual targeting and advertising. +This data is then added to the bid request by populating the OpenRTB 2.x objects `ortb2.site.content.data` (for IAB Content Taxonomy) and `ortb2.user.data` (for IAB Audience Taxonomy). This enrichment allows bidders to leverage Neuwo's contextual analysis for more precise targeting and decision-making. -The benefit of Neuwo AI RTD module is that it provides an alternative solution for advertisers to target their audiences and deliver relevant advertisements, as the widespread use of cookies for tracking and targeting is becoming increasingly limited. +Here is an example scheme of the data injected into the `ortb2` object by our module: -The RTD module uses cutting-edge algorithms to process real-time data, allowing advertisers to target their audiences based on contextual information, such as segments, IAB Tiers and brand safety. The RTD module is designed to be flexible and scalable, making it an ideal solution for advertisers looking to stay ahead of the curve in the post-cookie era. +```javascript +ortb2: { + site: { + content: { + // IAB Content Taxonomy data is injected here + data: [{ + name: "www.neuwo.ai", + segment: [{ + id: "274", + name: "Home & Garden", + }, + { + id: "42", + name: "Books and Literature", + }, + { + id: "210", + name: "Food & Drink", + }, + ], + ext: { + segtax: 7, + }, + }, ], + }, + }, + user: { + // IAB Audience Taxonomy data is injected here + data: [{ + name: "www.neuwo.ai", + segment: [{ + id: "49", + name: "Demographic | Gender | Female |", + }, + { + id: "161", + name: "Demographic | Marital Status | Married |", + }, + { + id: "6", + name: "Demographic | Age Range | 30-34 |", + }, + ], + ext: { + segtax: 4, + }, + }, ], + }, +} +``` -Generate your token at: [https://neuwo.ai/generatetoken/] +To get started, you can generate your API token at [https://neuwo.ai/generatetoken/](https://neuwo.ai/generatetoken/) or [contact us here](https://neuwo.ai/contact-us/). -# Configuration +## Configuration -```javascript +> **Important:** You must add the domain (origin) where Prebid.js is running to the list of allowed origins in Neuwo Edge API configuration. If you have problems, [contact us here](https://neuwo.ai/contact-us/). -const neuwoDataProvider = { - name: 'NeuwoRTDModule', - params: { - publicToken: '', - apiUrl: '' - } -} -pbjs.setConfig({realTimeData: { dataProviders: [ neuwoDataProvider ]}}) +This module is configured as part of the `realTimeData.dataProviders` object. +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'NeuwoRTDModule', + params: { + neuwoApiUrl: '', + neuwoApiToken: '', + iabContentTaxonomyVersion: '3.0', + } + }] + } +}); ``` -# Testing +**Parameters** -`gulp test --modules=rtdModule,neuwoRtdProvider` +| Name | Type | Required | Default | Description | +| :--------------------------------- | :----- | :------- | :------ | :------------------------------------------------------------------------------------------------ | +| `name` | String | Yes | | The name of the module, which is `NeuwoRTDModule`. | +| `params` | Object | Yes | | Container for module-specific parameters. | +| `params.neuwoApiUrl` | String | Yes | | The endpoint URL for the Neuwo Edge API. | +| `params.neuwoApiToken` | String | Yes | | Your unique API token provided by Neuwo. | +| `params.iabContentTaxonomyVersion` | String | No | `'3.0'` | Specifies the version of the IAB Content Taxonomy to be used. Supported values: `'2.2'`, `'3.0'`. | -## Add development tools if necessary +## Local Development -- Install node for npm -- run in prebid.js source folder: -`npm ci` -`npm i -g gulp-cli` +> **Linux** Linux might require exporting the following environment variable before running the commands below: +> `export CHROME_BIN=/usr/bin/chromium` -## Serve +You can run a local development server with the Neuwo module and a test bid adapter using the following command: -`gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter` +```bash +npx gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter +``` + +For a faster build without tests: + +```bash +npx gulp serve-fast --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --notests +``` + +After starting the server, you can access the example page at: +[http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html](http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html) -- in your browser, navigate to: +### Add development tools if necessary +If you don't have gulp-cli installed globally, run the following command in your Prebid.js source folder: +```bash +npm i -g gulp-cli +``` -`http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html` +## Testing +To run the module-specific tests: +```bash +npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/neuwoRtdProvider_spec.js +``` \ No newline at end of file diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index c400eea0429..1f75b1441e8 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -1,123 +1,587 @@ -import { server } from 'test/mocks/xhr.js'; -import * as neuwo from 'modules/neuwoRtdProvider'; +import * as neuwo from "modules/neuwoRtdProvider"; +import { server } from "test/mocks/xhr.js"; + +const NEUWO_API_URL = "https://api.url.neuwo.ai/edge/GetAiTopics"; +const NEUWO_API_TOKEN = "token"; +const IAB_CONTENT_TAXONOMY_VERSION = "3.0"; -const PUBLIC_TOKEN = 'public_key_0000'; const config = () => ({ params: { - publicToken: PUBLIC_TOKEN, - apiUrl: 'https://testing-requirement.neuwo.api' - } -}) - -const apiReturns = () => ({ - somethingExtra: { object: true }, - marketing_categories: { - iab_tier_1: [ - { ID: 'IAB21', label: 'Real Estate', relevance: '0.45699' } - ] - } -}) - -const TAX_ID = '441' + neuwoApiUrl: NEUWO_API_URL, + neuwoApiToken: NEUWO_API_TOKEN, + iabContentTaxonomyVersion: IAB_CONTENT_TAXONOMY_VERSION, + }, +}); + +function getNeuwoApiResponse() { + return { + brand_safety: { + BS_score: "1.0", + BS_indication: "yes", + }, + marketing_categories: { + iab_tier_1: [ + { + ID: "274", + label: "Home & Garden", + relevance: "0.47", + }, + ], + iab_tier_2: [ + { + ID: "216", + label: "Cooking", + relevance: "0.41", + }, + ], + iab_tier_3: [], + iab_audience_tier_3: [ + { + ID: "49", + label: "Demographic | Gender | Female |", + relevance: "0.9923", + }, + ], + iab_audience_tier_4: [ + { + ID: "127", + label: "Demographic | Household Data | 1 Child |", + relevance: "0.9673", + }, + ], + iab_audience_tier_5: [ + { + ID: "98", + label: "Demographic | Household Data | Parents with Children |", + relevance: "0.9066", + }, + ], + }, + smart_tags: [ + { + ID: "123", + name: "animals-group", + }, + ], + }; +} +const CONTENT_TIERS = ["iab_tier_1", "iab_tier_2", "iab_tier_3"]; +const AUDIENCE_TIERS = ["iab_audience_tier_3", "iab_audience_tier_4", "iab_audience_tier_5"]; /** * Object generator, like above, written using alternative techniques * @returns object with predefined (expected) bidsConfig fields */ function bidsConfiglike() { - return Object.assign({}, { - ortb2Fragments: { global: {} } - }) + return Object.assign( + {}, + { + ortb2Fragments: { global: {} }, + } + ); } -describe('neuwoRtdProvider', function () { - describe('neuwoRtdModule', function () { - it('initializes', function () { - expect(neuwo.neuwoRtdModule.init(config())).to.be.true; - }) - it('init needs that public token', function () { - expect(neuwo.neuwoRtdModule.init()).to.be.false; - }) - - describe('segment picking', function () { - it('handles bad inputs', function () { - expect(neuwo.pickSegments()).to.be.an('array').that.is.empty; - expect(neuwo.pickSegments('technically also an array')).to.be.an('array').that.is.empty; - expect(neuwo.pickSegments({ bad_object: 'bad' })).to.be.an('array').that.is.empty; - }) - it('handles malformations', function () { - const result = neuwo.pickSegments([{something_wrong: true}, null, { ID: 'IAB19-20' }, { id: 'IAB3-1', ID: 'IAB9-20' }]) - expect(result[0].id).to.equal('631') - expect(result[1].id).to.equal('58') - expect(result.length).to.equal(2) - }) - }) - - describe('topic injection', function () { - it('mutates bidsConfig', function () { - const topics = apiReturns() - const bidsConfig = bidsConfiglike() - neuwo.injectTopics(topics, bidsConfig, () => { }) - expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) - expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) - expect(bidsConfig.ortb2Fragments.global.site.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) - }) - - it('handles malformed responses', function () { - let topics = { message: 'Forbidden' } - const bidsConfig = bidsConfiglike() - neuwo.injectTopics(topics, bidsConfig, () => { }) - expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) - expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; - - topics = '404 wouldn\'t really even show up for injection' - const bdsConfig = bidsConfiglike() - neuwo.injectTopics(topics, bdsConfig, () => { }) - expect(bdsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) - expect(bdsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; - - topics = undefined - const bdsConfigE = bidsConfiglike() - neuwo.injectTopics(topics, bdsConfigE, () => { }) - expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) - expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; - }) - }) - - describe('fragment addition', function () { - it('mutates input objects', function () { - const alphabet = { a: { b: { c: {} } } } - neuwo.addFragment(alphabet.a.b.c, 'd.e.f', { g: 'h' }) - expect(alphabet.a.b.c.d.e.f.g).to.equal('h') - }) - }) - - describe('getBidRequestData', function () { - it('forms requests properly and mutates input bidsConfig', function () { - const bids = bidsConfiglike() - const conf = config() +describe("neuwoRtdModule", function () { + describe("init", function () { + it("should return true when all required parameters are provided", function () { + expect( + neuwo.neuwoRtdModule.init(config()), + "should successfully initialize with a valid configuration" + ).to.be.true; + }); + + it("should return false when no configuration is provided", function () { + expect(neuwo.neuwoRtdModule.init(), "should fail initialization if config is missing").to.be + .false; + }); + + it("should return false when the neuwoApiUrl parameter is missing", function () { + const incompleteConfig = { + params: { + neuwoApiToken: NEUWO_API_TOKEN, + }, + }; + expect( + neuwo.neuwoRtdModule.init(incompleteConfig), + "should fail initialization if neuwoApiUrl is not set" + ).to.be.false; + }); + + it("should return false when the neuwoApiToken parameter is missing", function () { + const incompleteConfig = { + params: { + neuwoApiUrl: NEUWO_API_URL, + }, + }; + expect( + neuwo.neuwoRtdModule.init(incompleteConfig), + "should fail initialization if neuwoApiToken is not set" + ).to.be.false; + }); + }); + + describe("buildIabData", function () { + it("should return an empty segment array when no matching tiers are found", function () { + const marketingCategories = getNeuwoApiResponse().marketing_categories; + const tiers = ["non_existent_tier"]; + const segtax = 0; + const result = neuwo.buildIabData(marketingCategories, tiers, segtax); + const expected = { + name: neuwo.DATA_PROVIDER, + segment: [], + ext: { + segtax, + }, + }; + expect(result, "should produce a valid object with an empty segment array").to.deep.equal( + expected + ); + }); + + it("should correctly build the data object for content tiers", function () { + const marketingCategories = getNeuwoApiResponse().marketing_categories; + const segtax = 0; + const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const expected = { + name: neuwo.DATA_PROVIDER, + segment: [ + { + id: "274", + name: "Home & Garden", + }, + { + id: "216", + name: "Cooking", + }, + ], + ext: { + segtax, + }, + }; + expect(result, "should aggregate segments from all specified content tiers").to.deep.equal( + expected + ); + }); + + it("should correctly build the data object for audience tiers", function () { + const marketingCategories = getNeuwoApiResponse().marketing_categories; + const segtax = 0; + const result = neuwo.buildIabData(marketingCategories, AUDIENCE_TIERS, segtax); + const expected = { + name: neuwo.DATA_PROVIDER, + segment: [ + { + id: "49", + name: "Demographic | Gender | Female |", + }, + { + id: "127", + name: "Demographic | Household Data | 1 Child |", + }, + { + id: "98", + name: "Demographic | Household Data | Parents with Children |", + }, + ], + ext: { + segtax, + }, + }; + expect(result, "should aggregate segments from all specified audience tiers").to.deep.equal( + expected + ); + }); + + it("should return an empty segment array when marketingCategories is null or undefined", function () { + const segtax = 4; + const expected = { + name: neuwo.DATA_PROVIDER, + segment: [], + ext: { + segtax, + }, + }; + expect( + neuwo.buildIabData(null, CONTENT_TIERS, segtax), + "should handle null marketingCategories gracefully" + ).to.deep.equal(expected); + expect( + neuwo.buildIabData(undefined, CONTENT_TIERS, segtax), + "should handle undefined marketingCategories gracefully" + ).to.deep.equal(expected); + }); + + it("should return an empty segment array when marketingCategories is empty", function () { + const marketingCategories = {}; + const segtax = 4; + const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const expected = { + name: neuwo.DATA_PROVIDER, + segment: [], + ext: { + segtax, + }, + }; + expect(result, "should handle an empty marketingCategories object").to.deep.equal(expected); + }); + + it("should gracefully handle if a marketing_categories key contains a non-array value", function () { + const marketingCategories = getNeuwoApiResponse().marketing_categories; + // Overwrite iab_tier_1 to be an object instead of an array + marketingCategories.iab_tier_1 = { ID: "274", label: "Home & Garden" }; + + const segtax = 4; + const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const expected = { + name: neuwo.DATA_PROVIDER, + // The segment should only contain data from the valid iab_tier_2 + segment: [ + { + id: "216", + name: "Cooking", + }, + ], + ext: { + segtax, + }, + }; + + expect(result, "should skip non-array tier values and process valid ones").to.deep.equal( + expected + ); + }); + + it("should ignore malformed objects within a tier array", function () { + const marketingCategories = getNeuwoApiResponse().marketing_categories; + // Overwrite iab_tier_1 with various malformed objects + marketingCategories.iab_tier_1 = [ + { ID: "274", label: "Valid Object" }, + { ID: "999" }, // Missing 'label' property + { label: "Another Label" }, // Missing 'ID' property + null, // A null value + "just-a-string", // A string primitive + {}, // An empty object + ]; + + const segtax = 4; + const result = neuwo.buildIabData(marketingCategories, CONTENT_TIERS, segtax); + const expected = { + name: neuwo.DATA_PROVIDER, + // The segment should contain the one valid object from iab_tier_1 and the data from iab_tier_2 + segment: [ + { + id: "274", + name: "Valid Object", + }, + { + id: "216", + name: "Cooking", + }, + ], + ext: { + segtax, + }, + }; + + expect(result, "should filter out malformed entries within a tier array").to.deep.equal( + expected + ); + }); + + it("should return an empty segment array if the entire marketingCategories object is not a valid object", function () { + const segtax = 4; + const expected = { + name: neuwo.DATA_PROVIDER, + segment: [], + ext: { segtax }, + }; + // Test with a string + const resultString = neuwo.buildIabData("incorrect format", CONTENT_TIERS, segtax); + expect(resultString, "should handle non-object marketingCategories input").to.deep.equal( + expected + ); + }); + }); + + describe("injectOrtbData", function () { + it("should correctly mutate the request bids config object with new data", function () { + const reqBidsConfigObj = { ortb2Fragments: { global: {} } }; + neuwo.injectOrtbData(reqBidsConfigObj, "c.d.e.f", { g: "h" }); + expect( + reqBidsConfigObj.ortb2Fragments.global.c.d.e.f.g, + "should deeply merge the new data into the target object" + ).to.equal("h"); + }); + }); + + describe("getBidRequestData", function () { + describe("when using IAB Content Taxonomy 3.0", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should correspond to IAB Content Taxonomy 3.0" + ).to.equal(7); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); + expect( + contentData.segment[1].name, + "The second segment name should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].label); + }); + }); + + describe("when using IAB Content Taxonomy 2.2", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.iabContentTaxonomyVersion = "2.2"; // control xhr api request target for testing - conf.params.argUrl = 'https://publisher.works/article.php?get=horrible_url_for_testing&id=5' + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; - neuwo.getBidRequestData(bids, () => { }, conf, 'any consent data works, clearly') + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should correspond to IAB Content Taxonomy 2.2" + ).to.equal(6); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); + expect( + contentData.segment[1].name, + "The second segment name should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].label); + }); + }); + + describe("when using the default IAB Content Taxonomy", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.iabContentTaxonomyVersion = undefined; + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); const request = server.requests[0]; - expect(request.url).to.be.a('string').that.includes(conf.params.publicToken) - expect(request.url).to.include(encodeURIComponent(conf.params.argUrl)) - request.respond(200, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify(apiReturns())); - - expect(bids.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) - expect(bids.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) - }) - - it('accepts detail not available result', function () { - const bidsConfig = bidsConfiglike() - const comparison = bidsConfiglike() - neuwo.getBidRequestData(bidsConfig, () => { }, config(), 'consensually') + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + const contentData = bidsConfig.ortb2Fragments.global.site.content.data[0]; + expect(contentData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + contentData.ext.segtax, + "The segtax value should default to IAB Content Taxonomy 3.0" + ).to.equal(7); + expect( + contentData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_tier_1[0].ID); + expect( + contentData.segment[1].name, + "The second segment name should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_tier_2[0].label); + }); + }); + + describe("when using IAB Audience Taxonomy 1.1", function () { + it("should correctly structure the bids object after a successful API response", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + const conf = config(); + // control xhr api request target for testing + conf.params.websiteToAnalyseUrl = + "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); const request = server.requests[0]; - request.respond(404, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify({ detail: 'Basically first time seeing this' })); - expect(bidsConfig).to.deep.equal(comparison) - }) - }) - }) -}) + + expect(request.url, "The request URL should be a string").to.be.a("string"); + expect(request.url, "The request URL should include the public API token").to.include( + conf.params.neuwoApiToken + ); + expect(request.url, "The request URL should include the encoded website URL").to.include( + encodeURIComponent(conf.params.websiteToAnalyseUrl) + ); + + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + const userData = bidsConfig.ortb2Fragments.global.user.data[0]; + expect(userData.name, "The data provider name should be correctly set").to.equal( + neuwo.DATA_PROVIDER + ); + expect( + userData.ext.segtax, + "The segtax value should correspond to IAB Audience Taxonomy 1.1" + ).to.equal(4); + expect( + userData.segment[0].id, + "The first segment ID should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_audience_tier_3[0].ID); + expect( + userData.segment[1].name, + "The second segment name should match the API response" + ).to.equal(apiResponse.marketing_categories.iab_audience_tier_4[0].label); + }); + }); + + it("should not change the bids object structure after an unsuccessful API response", function () { + const bidsConfig = bidsConfiglike(); + const bidsConfigCopy = bidsConfiglike(); + const conf = config(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 404, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify({ detail: "test error" }) + ); + expect( + bidsConfig, + "The bids config object should remain unmodified after a failed API call" + ).to.deep.equal(bidsConfigCopy); + }); + }); + + // NEW TESTS START HERE + describe("injectIabCategories edge cases and merging", function () { + it("should not inject data if 'marketing_categories' is missing from the successful API response", function () { + const apiResponse = { brand_safety: { BS_score: "1.0" } }; // Missing marketing_categories + const bidsConfig = bidsConfiglike(); + const bidsConfigCopy = bidsConfiglike(); + const conf = config(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // After a successful response with missing data, the global ortb2 fragments should remain empty + // as the data injection logic checks for marketingCategories. + expect( + bidsConfig.ortb2Fragments.global, + "The global ORTB fragments should remain empty" + ).to.deep.equal(bidsConfigCopy.ortb2Fragments.global); + }); + + it("should append content and user data to existing ORTB fragments", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig = bidsConfiglike(); + // Simulate existing first-party data from another source/module + const existingContentData = { name: "other_content_provider", segment: [{ id: "1" }] }; + const existingUserData = { name: "other_user_provider", segment: [{ id: "2" }] }; + + bidsConfig.ortb2Fragments.global = { + site: { + content: { + data: [existingContentData], + }, + }, + user: { + data: [existingUserData], + }, + }; + const conf = config(); + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + request.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + const siteData = bidsConfig.ortb2Fragments.global.site.content.data; + const userData = bidsConfig.ortb2Fragments.global.user.data; + + // Check that the existing data is still there (index 0) + expect(siteData[0], "Existing site.content.data should be preserved").to.deep.equal( + existingContentData + ); + expect(userData[0], "Existing user.data should be preserved").to.deep.equal(existingUserData); + + // Check that the new Neuwo data is appended (index 1) + expect(siteData.length, "site.content.data array should have 2 entries").to.equal(2); + expect(userData.length, "user.data array should have 2 entries").to.equal(2); + expect(siteData[1].name, "The appended content data should be from Neuwo").to.equal( + neuwo.DATA_PROVIDER + ); + expect(userData[1].name, "The appended user data should be from Neuwo").to.equal( + neuwo.DATA_PROVIDER + ); + }); + }); +}); From 426ddcc027d74b54382df72162f297276994eabc Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:14:28 +0200 Subject: [PATCH 046/147] RTB House Bid Adapter: add GPP support (#14047) --- modules/rtbhouseBidAdapter.js | 10 +- test/spec/modules/rtbhouseBidAdapter_spec.js | 122 +++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 83536bd0abe..f909f2302f9 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, deepClone, isArray, logError, mergeDeep, isEmpty, isPlainObject, isNumber, isStr} from '../src/utils.js'; +import {deepAccess, deepClone, isArray, logError, mergeDeep, isEmpty, isPlainObject, isNumber, isStr, deepSetValue} from '../src/utils.js'; import {getOrigin} from '../libraries/getOrigin/index.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -87,6 +87,14 @@ export const spec = { }); } + if (bidderRequest.gppConsent?.gppString) { + deepSetValue(request, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(request, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (ortb2Params.regs?.gpp) { + deepSetValue(request, 'regs.gpp', ortb2Params.regs.gpp); + deepSetValue(request, 'regs.gpp_sid', ortb2Params.regs.gpp_sid); + } + const computedEndpointUrl = ENDPOINT_URL; return { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 2f1fa127b28..f44ccd1651d 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -208,6 +208,128 @@ describe('RTBHouseAdapter', () => { expect(data.user.ext.consent).to.equal(''); }); + it('should populate GPP consent string when gppConsent.gppString is provided', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7] + } + }) + ); + const data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.gpp_sid).to.deep.equal([7]); + }); + + it('should populate GPP consent with multiple applicable sections', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [2, 6, 7] + } + }) + ); + const data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.gpp_sid).to.deep.equal([2, 6, 7]); + }); + + it('should fallback to ortb2.regs.gpp when gppConsent.gppString is not provided', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const localBidderRequest = { + ...bidderRequest, + ortb2: { + regs: { + gpp: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + gpp_sid: [8, 10] + } + } + }; + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.gpp_sid).to.deep.equal([8, 10]); + }); + + it('should prioritize gppConsent.gppString over ortb2.regs.gpp', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const localBidderRequest = { + ...bidderRequest, + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7] + }, + ortb2: { + regs: { + gpp: 'DIFFERENT_GPP_STRING', + gpp_sid: [8, 10] + } + } + }; + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.gpp_sid).to.deep.equal([7]); + }); + + it('should not populate GPP consent when neither gppConsent nor ortb2.regs.gpp is provided', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data).to.not.have.nested.property('regs.gpp'); + expect(data).to.not.have.nested.property('regs.gpp_sid'); + }); + + it('should not populate GPP when gppConsent exists but gppString is missing', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gppConsent: { + applicableSections: [7] + } + }) + ); + const data = JSON.parse(request.data); + expect(data).to.not.have.nested.property('regs.gpp'); + expect(data).to.not.have.nested.property('regs.gpp_sid'); + }); + + it('should handle both GDPR and GPP consent together', function () { + const bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + }, + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA', + applicableSections: [7] + } + }) + ); + const data = JSON.parse(request.data); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ-A'); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA'); + expect(data.regs.gpp_sid).to.deep.equal([7]); + }); + it('should include banner imp in request', () => { const bidRequest = Object.assign([], bidRequests); const request = spec.buildRequests(bidRequest, bidderRequest); From 6d94d806be96a80c6c22d332c2b76cc1f4559f16 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Wed, 22 Oct 2025 02:15:37 +0200 Subject: [PATCH 047/147] Richaudience Bid Adapter: add compatibility with gpid (#14042) * Richaudience Bid Adapter: test/spec change in user eids * Richaudience Bid Adapter: change in user eids * RichAudience Bid Adapter : remove deprecated params for video player * Richaudience Bid Adapter: add compatibility with gpid * Richaudience Bid Adapter Test: add compatibility with gpid * Richaudience Bid Adapter: add compatibility with gpid --------- Co-authored-by: IAN --- modules/richaudienceBidAdapter.js | 4 +- .../modules/richaudienceBidAdapter_spec.js | 44 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 7d1db618780..8928bc5a214 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -291,7 +291,9 @@ function raiGetResolution() { function raiSetPbAdSlot(bid) { let pbAdSlot = ''; - if (deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') != null) { + if (deepAccess(bid, 'ortb2Imp.ext.data.gpid') != null) { + pbAdSlot = deepAccess(bid, 'ortb2Imp.ext.data.gpid') + } else if (deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') != null) { pbAdSlot = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') } return pbAdSlot diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index fd75c6d0c4d..ce6879257ef 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -87,7 +87,36 @@ describe('Richaudience adapter tests', function () { bidId: '2c7c8e9c900244', ortb2Imp: { ext: { - gpid: '/19968336/header-bid-tag-1#example-2', + data: { + gpid: '/19968336/header-bid-tag-1#example-2', + } + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_NEW_SIZES_PBADSLOT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + ortb2Imp: { + ext: { data: { pbadslot: '/19968336/header-bid-tag-1#example-2' } @@ -863,7 +892,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent.dsa.transparency[0]).to.have.property('domain').and.to.equal('richaudience.com'); }) - it('should pass gpid', function () { + it('should pass gpid with gpid', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -874,6 +903,17 @@ describe('Richaudience adapter tests', function () { const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); }) + it('should pass gpid with pbadslot', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_PBADSLOT, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); + }) describe('onTimeout', function () { beforeEach(function () { From 053df9f227a54ea2b8716c13c1b66c4cb485d0d0 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 21 Oct 2025 22:05:43 -0400 Subject: [PATCH 048/147] Core: consistentTids feature (#14011) * Add assertions for consistent tid prefixes * Respect publisher supplied impression tids * Core: align tidSource redaction with review * Core: allow pub tidSource option * Core: adjust tid prefixes for consistent option * refactor & fix some edge cases * Rename consistentTids to consistentTIDs for consistency * Fix typo in config key from 'consistentTids' to 'consistentTIDs' * Fix typo in consistentTIDs configuration --------- Co-authored-by: Demetrio Girardi --- src/activities/redactor.ts | 9 +- src/adapterManager.ts | 71 ++++++---- src/types/ortb/request.d.ts | 16 ++- test/spec/activities/redactor_spec.js | 4 +- test/spec/unit/core/adapterManager_spec.js | 145 +++++++++++++++------ 5 files changed, 177 insertions(+), 68 deletions(-) diff --git a/src/activities/redactor.ts b/src/activities/redactor.ts index 30ce6e5636b..d58dd3588ce 100644 --- a/src/activities/redactor.ts +++ b/src/activities/redactor.ts @@ -134,7 +134,7 @@ function bidRequestTransmitRules(isAllowed = isActivityAllowed) { }, { name: ACTIVITY_TRANSMIT_TID, - paths: ['ortb2Imp.ext.tid'], + paths: ['ortb2Imp.ext.tid', 'ortb2Imp.ext.tidSource'], applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_TID, isAllowed) } ].map(redactRule) @@ -178,7 +178,7 @@ export function ortb2TransmitRules(isAllowed = isActivityAllowed) { }, { name: ACTIVITY_TRANSMIT_TID, - paths: ['source.tid'], + paths: ['source.tid', 'source.ext.tidSource'], applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_TID, isAllowed), } ].map(redactRule); @@ -214,6 +214,11 @@ declare module '../config' { * privacy concern. Since version 8 they are disabled by default, and can be re-enabled with this flag. */ enableTIDs?: boolean; + /** + * When enabled alongside enableTIDs, bidders receive a consistent source.tid for an auction rather than + * bidder-specific values. + */ + consistentTIDs?: boolean; } } // by default, TIDs are off since version 8 diff --git a/src/adapterManager.ts b/src/adapterManager.ts index a438fa06bcd..17e845a765b 100644 --- a/src/adapterManager.ts +++ b/src/adapterManager.ts @@ -220,7 +220,7 @@ type GetBidsOptions = { adUnits: (SRC extends typeof S2S.SRC ? PBSAdUnit : AdUnit)[] src: SRC; metrics: Metrics, - tids: { [bidderCode: BidderCode]: string }; + getTid: ReturnType; } export type AliasBidderOptions = { @@ -244,7 +244,7 @@ export type AnalyticsAdapter

= StorageDisclosure & gvlid?: number | ((config: AnalyticsConfig

) => number); } -function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics, tids}: GetBidsOptions): BidRequest[] { +function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics, getTid}: GetBidsOptions): BidRequest[] { return adUnits.reduce((result, adUnit) => { const bids = adUnit.bids.filter(bid => bid.bidder === bidderCode); if (bidderCode == null && bids.length === 0 && (adUnit as PBSAdUnit).s2sBid != null) { @@ -252,11 +252,15 @@ function getBids({bidde } result.push( bids.reduce((bids: BidRequest[], bid: BidRequest) => { - if (!tids.hasOwnProperty(adUnit.transactionId)) { - tids[adUnit.transactionId] = generateUUID(); - } + const [tid, tidSource] = getTid(bid.bidder, adUnit.transactionId, bid.ortb2Imp?.ext?.tid ?? adUnit.ortb2Imp?.ext?.tid); bid = Object.assign({}, bid, - {ortb2Imp: mergeDeep({}, adUnit.ortb2Imp, bid.ortb2Imp, {ext: {tid: tids[adUnit.transactionId]}})}, + { + ortb2Imp: mergeDeep( + {}, + adUnit.ortb2Imp, + bid.ortb2Imp, + {ext: {tid, tidSource}}) + }, getDefinedParams(adUnit, ADUNIT_BID_PROPERTIES), ); @@ -440,6 +444,35 @@ declare module './hook' { } } +function tidFactory() { + const consistent = !!config.getConfig('consistentTIDs'); + let tidSource, getTid; + if (consistent) { + tidSource = 'pbjsStable'; + getTid = (saneTid) => saneTid + } else { + tidSource = 'pbjs'; + getTid = (() => { + const tids = {}; + return (saneTid, bidderCode) => { + if (!tids.hasOwnProperty(bidderCode)) { + tids[bidderCode] = {}; + } + if (!tids[bidderCode].hasOwnProperty(saneTid)) { + tids[bidderCode][saneTid] = `u${generateUUID()}`; + } + return tids[bidderCode][saneTid]; + } + })(); + } + return function (bidderCode, saneTid, fpdTid) { + return [ + fpdTid ?? getTid(saneTid, bidderCode), + fpdTid != null ? 'pub' : tidSource + ] + } +} + const adapterManager = { bidderRegistry: _bidderRegistry, analyticsRegistry: _analyticsRegistry, @@ -493,16 +526,7 @@ const adapterManager = { const ortb2 = ortb2Fragments.global || {}; const bidderOrtb2 = ortb2Fragments.bidder || {}; - const sourceTids: any = {}; - const extTids: any = {}; - - function tidFor(tids, bidderCode, makeTid) { - const tid = tids.hasOwnProperty(bidderCode) ? tids[bidderCode] : makeTid(); - if (bidderCode != null) { - tids[bidderCode] = tid; - } - return tid; - } + const getTid = tidFactory(); function addOrtb2>(bidderRequest: Partial, s2sActivityParams?): T { const redact = dep.redact( @@ -510,12 +534,17 @@ const adapterManager = { ? s2sActivityParams : activityParams(MODULE_TYPE_BIDDER, bidderRequest.bidderCode) ); - const tid = tidFor(sourceTids, bidderRequest.bidderCode, generateUUID); + 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}} + { + source: { + tid, + ext: {tidSource} + } + } ))); bidderRequest.ortb2 = fpd; bidderRequest.bids = bidderRequest.bids.map((bid) => { @@ -534,7 +563,6 @@ const adapterManager = { const uniquePbsTid = generateUUID(); (serverBidders.length === 0 && hasModuleBids ? [null] : serverBidders).forEach(bidderCode => { - const tids = tidFor(extTids, bidderCode, () => ({})); const bidderRequestId = generateUUID(); const metrics = auctionMetrics.fork(); const bidderRequest = addOrtb2({ @@ -549,7 +577,7 @@ const adapterManager = { 'adUnits': deepClone(adUnitsS2SCopy), src: S2S.SRC, metrics, - tids + getTid, }), auctionStart: auctionStart, timeout: s2sConfig.timeout, @@ -582,7 +610,6 @@ const adapterManager = { // client adapters const adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); clientBidders.forEach(bidderCode => { - const tids = tidFor(extTids, bidderCode, () => ({})); const bidderRequestId = generateUUID(); const metrics = auctionMetrics.fork(); const bidderRequest = addOrtb2({ @@ -596,7 +623,7 @@ const adapterManager = { 'adUnits': deepClone(adUnitsClientCopy), src: 'client', metrics, - tids + getTid, }), auctionStart: auctionStart, timeout: cbTimeout, diff --git a/src/types/ortb/request.d.ts b/src/types/ortb/request.d.ts index 320e13965ed..64cf842ccb1 100644 --- a/src/types/ortb/request.d.ts +++ b/src/types/ortb/request.d.ts @@ -5,9 +5,16 @@ import type {DSARequest} from "./ext/dsa.d.ts"; import type {BidRequest, Imp} from 'iab-openrtb/v26'; +type TidSource = 'pbjs' | 'pbjsStable' | 'pub'; + export interface ORTBRequest extends BidRequest { + source: BidRequest['source'] & { + ext: Ext & { + tidSource: TidSource + } + } ext: Ext & { - dsa?: DSARequest + dsa?: DSARequest; } } @@ -30,5 +37,12 @@ export type ORTBImp = Imp & { * but common across all requests for that opportunity. */ tid?: string; + /** + * Indicates which entity generated the TID. + * - pbjs: Prebid generated the TID and it is bidder-specific. + * - pbjsStable: Prebid generated the TID and it is consistent across bidders. + * - pub: The publisher supplied the TID. + */ + tidSource?: TidSource; } }; diff --git a/test/spec/activities/redactor_spec.js b/test/spec/activities/redactor_spec.js index c5c194da73d..e1d565d6d60 100644 --- a/test/spec/activities/redactor_spec.js +++ b/test/spec/activities/redactor_spec.js @@ -276,7 +276,7 @@ describe('redactor', () => { }); testAllowDeny(ACTIVITY_TRANSMIT_TID, (allowed) => { - testPropertiesAreRemoved(() => redactor.bidRequest, ['ortb2Imp.ext.tid'], allowed); + testPropertiesAreRemoved(() => redactor.bidRequest, ['ortb2Imp.ext.tid', 'ortb2Imp.ext.tidSource'], allowed); }) }); @@ -290,7 +290,7 @@ describe('redactor', () => { }); testAllowDeny(ACTIVITY_TRANSMIT_TID, (allowed) => { - testPropertiesAreRemoved(() => redactor.ortb2, ['source.tid'], allowed); + testPropertiesAreRemoved(() => redactor.ortb2, ['source.tid', 'source.ext.tidSource'], allowed); }); testAllowDeny(ACTIVITY_TRANSMIT_PRECISE_GEO, (allowed) => { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index e37a41ad510..c8d49c15548 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -2099,50 +2099,113 @@ describe('adapterManager tests', function () { return adapterManager.makeBidRequests(adUnits, 0, 'mockAuctionId', 1000, [], ortb2Fragments); } - it('should NOT populate source.tid with auctionId', () => { - const reqs = makeRequests(); - expect(reqs[0].ortb2.source.tid).to.not.equal('mockAuctionId'); - }); - it('should override source.tid if specified in FPD', () => { - const reqs = makeRequests({ - global: { - source: { - tid: 'tid' - } - }, - rubicon: { - source: { - tid: 'tid' - } - } + Object.entries({ + disabled() {}, + consistent() { + config.setConfig({ + enableTIDs: true, + consistentTIDs: true, + }) + }, + inconsistent() { + config.setConfig({ + enableTIDs: true + }) + } + }).forEach(([t, setup]) => { + describe(`when TIDs are ${t}`, () => { + beforeEach(setup); + afterEach(() => { + config.resetConfig() + }); + it('should respect source.tid from FPD', () => { + const reqs = makeRequests({ + global: { + source: { + tid: 'tid' + } + }, + bidder: { + rubicon: { + source: { + tid: 'tid2' + } + } + } + }); + reqs.forEach(req => { + expect(req.ortb2.source.tid).to.eql(req.bidderCode === 'rubicon' ? 'tid2' : 'tid'); + expect(req.ortb2.source.ext.tidSource).to.eql('pub'); + }); + }) + it('should respect publisher-provided ortb2Imp.ext.tid values', () => { + adUnits[1].ortb2Imp = {ext: {tid: 'pub-tid'}}; + const tidRequests = makeRequests().flatMap(br => br.bids).filter(req => req.adUnitCode === adUnits[1].code); + expect(tidRequests.length).to.eql(2); + tidRequests.forEach(req => { + expect(req.ortb2Imp.ext.tid).to.eql('pub-tid'); + expect(req.ortb2Imp.ext.tidSource).to.eql('pub'); + }) + }); + }) + }) + describe('when tids are enabled', () => { + beforeEach(() => { + config.setConfig({enableTIDs: true}); + }) + afterEach(() => { + config.resetConfig(); }); - reqs.forEach(req => { - expect(req.ortb2.source.tid).to.exist; - expect(req.ortb2.source.tid).to.not.eql('tid'); + + it('should populate source.tid', () => { + makeRequests().forEach(req => { + expect(req.ortb2.source.tid).to.exist; + }); + }) + + it('should generate ortb2Imp.ext.tid', () => { + makeRequests().flatMap(br => br.bids).forEach(req => { + expect(req.ortb2Imp.ext.tid).to.exist; + }) }); - expect(reqs[0].ortb2.source.tid).to.not.eql(reqs[1].ortb2.source.tid); - }); - it('should generate ortb2Imp.ext.tid', () => { - const reqs = makeRequests(); - const tids = new Set(reqs.flatMap(br => br.bids).map(b => b.ortb2Imp?.ext?.tid)); - expect(tids.size).to.eql(3); - }); - it('should override ortb2Imp.ext.tid if specified in FPD', () => { - adUnits[0].ortb2Imp = adUnits[1].ortb2Imp = { - ext: { - tid: 'tid' - } - }; - const reqs = makeRequests(); - expect(reqs[0].bids[0].ortb2Imp.ext.tid).to.not.eql('tid'); - }); - it('should use matching ext.tid if transactionId match', () => { - adUnits[1].transactionId = adUnits[0].transactionId; - const reqs = makeRequests(); - reqs.forEach(br => { - expect(new Set(br.bids.map(b => b.ortb2Imp.ext.tid)).size).to.eql(1); + + describe('and inconsistent', () => { + it('should NOT populate source.tid with auctionId', () => { + const reqs = makeRequests(); + expect(reqs[0].ortb2.source.tid).to.not.equal('mockAuctionId'); + expect(reqs[0].ortb2.source.ext.tidSource).to.eql('pbjs') + }); + it('should provide different source.tid to different bidders', () => { + const reqs = makeRequests(); + expect(reqs[0].ortb2.source.tid).to.not.equal(reqs[1].ortb2.source.tid); + }); + it('should provide different ortb2Imp.ext.tid to different bidders', () => { + const reqs = makeRequests().flatMap(br => br.bids).filter(br => br.adUnitCode === adUnits[1].code); + expect(reqs[0].ortb2Imp.ext.tid).to.not.eql(reqs[1].ortb2Imp.ext.tid); + reqs.forEach(req => { + expect(req.ortb2Imp.ext.tidSource).to.eql('pbjs'); + }) + }); + }); + describe('and consistent', () => { + beforeEach(() => { + config.setConfig({consistentTIDs: true}); + }); + it('should populate source.tid with auctionId', () => { + const reqs = makeRequests(); + expect(reqs[0].ortb2.source.tid).to.eql('mockAuctionId'); + expect(reqs[0].ortb2.source.ext.tidSource).to.eql('pbjsStable'); + }); + it('should provide the same ext.tid to all bidders', () => { + const reqs = makeRequests().flatMap(br => br.bids).filter(req => req.adUnitCode === adUnits[1].code); + expect(reqs[0].ortb2Imp.ext.tid).to.eql(reqs[1].ortb2Imp.ext.tid); + reqs.forEach(req => { + expect(req.ortb2Imp.ext.tid).to.exist; + expect(req.ortb2Imp.ext.tidSource).to.eql('pbjsStable'); + }) + }) }) - }); + }) describe('when the same bidder is routed to both client and server', () => { function route(next) { next.bail({ From 5fdc56dd204a57d38ed2584b2e3337c0143d18cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petric=C4=83=20Nanc=C4=83?= Date: Wed, 22 Oct 2025 18:37:58 +0300 Subject: [PATCH 049/147] Fix mappings for the natives according to the standard (#14053) --- modules/sevioBidAdapter.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index 103c2c2c7f7..eafca8b96ec 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -26,18 +26,18 @@ const parseNativeAd = function (bid) { if (asset.data) { const value = asset.data.value; switch (asset.data.type) { - case 1: if (value) native.sponsoredBy = value; break; - case 2: if (value) native.body = value; break; + case 1: if (value) native.sponsored = value; break; + case 2: if (value) native.desc = value; break; case 3: if (value) native.rating = value; break; case 4: if (value) native.likes = value; break; case 5: if (value) native.downloads = value; break; case 6: if (value) native.price = value; break; - case 7: if (value) native.salePrice = value; break; + case 7: if (value) native.saleprice = value; break; case 8: if (value) native.phone = value; break; case 9: if (value) native.address = value; break; - case 10: if (value) native.body2 = value; break; - case 11: if (value) native.displayUrl = value; break; - case 12: if (value) native.cta = value; break; + case 10: if (value) native.desc2 = value; break; + case 11: if (value) native.displayurl = value; break; + case 12: if (value) native.ctatext = value; break; default: break; } } From 5a72ddd02f3a05c436056cf9a9575ad8288b78f7 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 22 Oct 2025 12:06:51 -0400 Subject: [PATCH 050/147] CI: attempt to serialize test runs (#14049) * Try to gauge running session from browserstack * automate, not app automate; separate to its own workflow * reintroduce retries * check on every chunk * jobs not steps * do not wait when not using browserstack * composite actions * remove stale yml * fix path --- .../actions/wait-for-browserstack/action.yml | 32 +++++++++++++++++++ .github/workflows/test-chunk.yml | 9 +++++- .github/workflows/test.yml | 9 +++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .github/actions/wait-for-browserstack/action.yml diff --git a/.github/actions/wait-for-browserstack/action.yml b/.github/actions/wait-for-browserstack/action.yml new file mode 100644 index 00000000000..63d24b87f88 --- /dev/null +++ b/.github/actions/wait-for-browserstack/action.yml @@ -0,0 +1,32 @@ +name: Wait for browserstack sessions +description: Wait until enough browserstack sessions have become available +inputs: + BROWSERSTACK_USER_NAME: + description: "Browserstack user name" + BROWSERSTACK_ACCESS_KEY: + description: "Browserstack access key" + +runs: + using: 'composite' + steps: + - env: + BROWSERSTACK_USERNAME: ${{ inputs.BROWSERSTACK_USER_NAME }} + BROWSERSTACK_ACCESS_KEY: ${{ inputs.BROWSERSTACK_ACCESS_KEY }} + shell: bash + run: | + while + status=$(curl -u "${BROWSERSTACK_USERNAME}:${BROWSERSTACK_ACCESS_KEY}" \ + -X GET "https://api-cloud.browserstack.com/automate/plan.json" 2> /dev/null); + running=$(jq '.parallel_sessions_running' <<< $status) + max_running=$(jq '.parallel_sessions_max_allowed' <<< $status) + queued=$(jq '.queued_sessions' <<< $status) + max_queued=$(jq '.queued_sessions_max_allowed' <<< $status) + spare=$(( ${max_running} + ${max_queued} - ${running} - ${queued} )) + required=6 + echo "Browserstack status: ${running} sessions running, ${queued} queued, ${spare} free" + (( ${required} > ${spare} )) + do + delay=$(( 60 + $(shuf -i 1-60 -n 1) )) + echo "Waiting for ${required} sessions to free up, checking again in ${delay}s" + sleep $delay + done diff --git a/.github/workflows/test-chunk.yml b/.github/workflows/test-chunk.yml index 12c27a370de..900bde960ee 100644 --- a/.github/workflows/test-chunk.yml +++ b/.github/workflows/test-chunk.yml @@ -67,11 +67,18 @@ jobs: local-testing: start local-identifier: random + - name: 'Wait for browserstack' + if: ${{ inputs.serialize }} + uses: ./.github/actions/wait-for-browserstack + with: + BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + - name: Run tests uses: nick-fields/retry@v3 with: timeout_minutes: 8 - max_attempts: 1 + max_attempts: 3 command: ${{ inputs.cmd }} - name: 'BrowserStackLocal Stop' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 422eb16bcaf..d243a67ca5d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -110,6 +110,7 @@ jobs: secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + test-e2e: name: "End-to-end tests" needs: checkout @@ -145,11 +146,17 @@ jobs: local-testing: start local-identifier: random + - name: 'Wait for browserstack' + uses: ./.github/actions/wait-for-browserstack + with: + BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + - name: Run tests uses: nick-fields/retry@v3 with: timeout_minutes: 20 - max_attempts: 1 + max_attempts: 3 command: npx gulp e2e-test - name: 'BrowserStackLocal Stop' From 757ff72adb6125dcf113945396ac22d07f2b2b55 Mon Sep 17 00:00:00 2001 From: Graham Higgins Date: Wed, 22 Oct 2025 18:03:46 -0400 Subject: [PATCH 051/147] Core: Skip module bids during mediaType eligibility checks (#14058) * Core: Skip module bids during mediaType eligibility checks Module bids do not have a `bidder` property. They should not be considered for mediaType checks. This only works as-is for banner units because we default `supportedMediaTypes` to `['banner']'` for an `undefined` bidder. Closes #14057 * add test case --------- Co-authored-by: Demetrio Girardi --- src/prebid.ts | 2 +- test/spec/unit/pbjs_api_spec.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/prebid.ts b/src/prebid.ts index 1f22f86915a..571f4106feb 100644 --- a/src/prebid.ts +++ b/src/prebid.ts @@ -850,7 +850,7 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); // get the bidder's mediaTypes - const allBidders = adUnit.bids.map(bid => bid.bidder); + const allBidders = adUnit.bids.map(bid => bid.bidder).filter(Boolean); const bidderRegistry = adapterManager.bidderRegistry; const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 5d852ef4e2e..3a439f7eb04 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -2849,6 +2849,18 @@ describe('Unit: Prebid Module', function () { // only appnexus supports native expect(biddersCalled.length).to.equal(1); }); + + it('module bids should not be filtered out', async () => { + delete adUnits[0].mediaTypes.banner; + adUnits[0].bids.push({ + module: 'pbsBidAdapter', + ortb2Imp: {} + }); + + pbjs.requestBids({adUnits}); + await auctionStarted; + expect(adapterManager.callBids.getCall(0).args[0][0].bids.length).to.eql(2); + }) }); describe('part 2', function () { From 2870230d47be17201df3812782083a54b462774a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 23 Oct 2025 10:32:39 -0400 Subject: [PATCH 052/147] Core: wait for creative document DOMContentLoaded (#13991) Co-authored-by: Patrick McCann --- src/adRendering.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/adRendering.ts b/src/adRendering.ts index 00ce5ac5b9d..b35959c75e0 100644 --- a/src/adRendering.ts +++ b/src/adRendering.ts @@ -351,12 +351,24 @@ export function renderAdDirect(doc, adId, options) { } const messageHandler = creativeMessageHandler({resizeFn}); + function waitForDocumentReady(doc) { + return new PbPromise((resolve) => { + if (doc.readyState === 'loading') { + doc.addEventListener('DOMContentLoaded', resolve); + } else { + resolve(); + } + }) + } + function renderFn(adData) { - getCreativeRenderer(bid) - .then(render => render(adData, { - sendMessage: (type, data) => messageHandler(type, data, bid), - mkFrame: createIframe, - }, doc.defaultView)) + PbPromise.all([ + getCreativeRenderer(bid), + waitForDocumentReady(doc) + ]).then(([render]) => render(adData, { + sendMessage: (type, data) => messageHandler(type, data, bid), + mkFrame: createIframe, + }, doc.defaultView)) .then( () => emitAdRenderSucceeded({doc, bid, id: bid.adId}), (e) => { From 87f3c27d5fbed952e3604fff25959e20981750cc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 23 Oct 2025 10:39:57 -0400 Subject: [PATCH 053/147] CI: update codeQL rules for fingerprinting APIs (#14059) * Core: Skip module bids during mediaType eligibility checks Module bids do not have a `bidder` property. They should not be considered for mediaType checks. This only works as-is for banner units because we default `supportedMediaTypes` to `['banner']'` for an `undefined` bidder. Closes #14057 * add test case * Improve codeql queries * event tracking * Extend and update codeQL rules for fingerprinting APIs --------- Co-authored-by: Graham Higgins --- ...utogen_2d_RenderingContext_getImageData.ql | 2 +- ...togen_2d_RenderingContext_isPointInPath.ql | 2 +- ...autogen_2d_RenderingContext_measureText.ql | 2 +- .../queries/autogen_AudioWorkletNode.ql | 15 ++++ .../autogen_DeviceMotionEvent_acceleration.ql | 15 ---- ...otionEvent_accelerationIncludingGravity.ql | 15 ---- .../autogen_DeviceMotionEvent_rotationRate.ql | 15 ---- .github/codeql/queries/autogen_Gyroscope.ql | 4 +- .github/codeql/queries/autogen_Gyroscope_x.ql | 4 +- .github/codeql/queries/autogen_Gyroscope_y.ql | 4 +- .github/codeql/queries/autogen_Gyroscope_z.ql | 4 +- ...gen_Intl-DateTimeFormat_resolvedOptions.ql | 16 +++++ .../autogen_Notification_permission.ql | 2 +- .../queries/autogen_OfflineAudioContext.ql | 4 +- .../queries/autogen_RTCPeerConnection.ql | 4 +- .../codeql/queries/autogen_SharedWorker.ql | 4 +- ...ogen_domEvent_devicemotion_acceleration.ql | 16 +++++ ...vicemotion_accelerationIncludingGravity.ql | 16 +++++ ...ogen_domEvent_devicemotion_rotationRate.ql | 16 +++++ ...gen_domEvent_deviceorientation_absolute.ql | 16 +++++ ...utogen_domEvent_deviceorientation_alpha.ql | 16 +++++ ...autogen_domEvent_deviceorientation_beta.ql | 16 +++++ ...utogen_domEvent_deviceorientation_gamma.ql | 16 +++++ .../queries/autogen_navigator_appCodeName.ql | 2 +- .../autogen_navigator_cookieEnabled.ql | 2 +- .../queries/autogen_navigator_deviceMemory.ql | 2 +- .../queries/autogen_navigator_getBattery.ql | 2 +- .../queries/autogen_navigator_getGamepads.ql | 2 +- .../autogen_navigator_hardwareConcurrency.ql | 2 +- .../queries/autogen_navigator_keyboard.ql | 2 +- .../autogen_navigator_mediaCapabilities.ql | 2 +- .../queries/autogen_navigator_mediaDevices.ql | 2 +- .../queries/autogen_navigator_onLine.ql | 2 +- .../queries/autogen_navigator_permissions.ql | 2 +- .../queries/autogen_navigator_productSub.ql | 2 +- ...n_navigator_requestMediaKeySystemAccess.ql | 2 +- .../queries/autogen_navigator_storage.ql | 2 +- .../queries/autogen_navigator_vendorSub.ql | 2 +- .../queries/autogen_navigator_webdriver.ql | 2 +- ...togen_navigator_webkitPersistentStorage.ql | 2 +- ...utogen_navigator_webkitTemporaryStorage.ql | 2 +- .../queries/autogen_screen_availHeight.ql | 2 +- .../queries/autogen_screen_availLeft.ql | 2 +- .../codeql/queries/autogen_screen_availTop.ql | 2 +- .../queries/autogen_screen_availWidth.ql | 2 +- .../queries/autogen_screen_colorDepth.ql | 2 +- .../queries/autogen_screen_orientation.ql | 2 +- .../queries/autogen_screen_pixelDepth.ql | 2 +- .../codeql/queries/autogen_sensor__start.ql | 16 +++++ ...2_RenderingContext_getContextAttributes.ql | 2 +- ...en_webgl2_RenderingContext_getExtension.ql | 2 +- ...en_webgl2_RenderingContext_getParameter.ql | 2 +- ...nderingContext_getShaderPrecisionFormat.ql | 2 +- ...RenderingContext_getSupportedExtensions.ql | 2 +- ...ogen_webgl2_RenderingContext_readPixels.ql | 2 +- ...l_RenderingContext_getContextAttributes.ql | 2 +- ...gen_webgl_RenderingContext_getExtension.ql | 2 +- ...gen_webgl_RenderingContext_getParameter.ql | 2 +- ...nderingContext_getShaderPrecisionFormat.ql | 2 +- ...RenderingContext_getSupportedExtensions.ql | 2 +- ...togen_webgl_RenderingContext_readPixels.ql | 2 +- .../autogen_window_devicePixelRatio.ql | 2 +- .../queries/autogen_window_indexedDB.ql | 2 +- .../queries/autogen_window_openDatabase.ql | 2 +- .../queries/autogen_window_outerHeight.ql | 2 +- .../queries/autogen_window_outerWidth.ql | 2 +- .../queries/autogen_window_screenLeft.ql | 2 +- .../queries/autogen_window_screenTop.ql | 2 +- .../codeql/queries/autogen_window_screenX.ql | 2 +- .../codeql/queries/autogen_window_screenY.ql | 2 +- .github/codeql/queries/domEvent.qll | 34 +++++++++ .github/codeql/queries/prebid.qll | 71 +++++++++++++++---- .github/codeql/queries/sensor.qll | 22 ++++++ fingerprintApis.mjs | 68 +++++++++++++----- 74 files changed, 385 insertions(+), 142 deletions(-) create mode 100644 .github/codeql/queries/autogen_AudioWorkletNode.ql delete mode 100644 .github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql delete mode 100644 .github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql delete mode 100644 .github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql create mode 100644 .github/codeql/queries/autogen_Intl-DateTimeFormat_resolvedOptions.ql create mode 100644 .github/codeql/queries/autogen_domEvent_devicemotion_acceleration.ql create mode 100644 .github/codeql/queries/autogen_domEvent_devicemotion_accelerationIncludingGravity.ql create mode 100644 .github/codeql/queries/autogen_domEvent_devicemotion_rotationRate.ql create mode 100644 .github/codeql/queries/autogen_domEvent_deviceorientation_absolute.ql create mode 100644 .github/codeql/queries/autogen_domEvent_deviceorientation_alpha.ql create mode 100644 .github/codeql/queries/autogen_domEvent_deviceorientation_beta.ql create mode 100644 .github/codeql/queries/autogen_domEvent_deviceorientation_gamma.ql create mode 100644 .github/codeql/queries/autogen_sensor__start.ql create mode 100644 .github/codeql/queries/domEvent.qll create mode 100644 .github/codeql/queries/sensor.qll diff --git a/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql b/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql index 48e6392d2ee..c2ab7478397 100644 --- a/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql +++ b/.github/codeql/queries/autogen_2d_RenderingContext_getImageData.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("2d") and api = invocation.getAPropertyRead("getImageData") -select api, "getImageData is an indicator of fingerprinting, weighed 35.4 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getImageData is an indicator of fingerprinting, weighed 40.74 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql b/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql index f7e181c124d..99892031f00 100644 --- a/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql +++ b/.github/codeql/queries/autogen_2d_RenderingContext_isPointInPath.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("2d") and api = invocation.getAPropertyRead("isPointInPath") -select api, "isPointInPath is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "isPointInPath is an indicator of fingerprinting, weighed 5043.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql b/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql index e4c72a07434..dd5a644cb95 100644 --- a/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql +++ b/.github/codeql/queries/autogen_2d_RenderingContext_measureText.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("2d") and api = invocation.getAPropertyRead("measureText") -select api, "measureText is an indicator of fingerprinting, weighed 44.88 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "measureText is an indicator of fingerprinting, weighed 45.5 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_AudioWorkletNode.ql b/.github/codeql/queries/autogen_AudioWorkletNode.ql new file mode 100644 index 00000000000..cc8f959b0f1 --- /dev/null +++ b/.github/codeql/queries/autogen_AudioWorkletNode.ql @@ -0,0 +1,15 @@ +/** + * @id prebid/audioworkletnode + * @name Use of AudioWorkletNode + * @kind problem + * @problem.severity warning + * @description Finds uses of AudioWorkletNode + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import prebid +from SourceNode api +where + api = callTo("AudioWorkletNode") +select api, "AudioWorkletNode is an indicator of fingerprinting, weighed 17.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql b/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql deleted file mode 100644 index 61504f043cb..00000000000 --- a/.github/codeql/queries/autogen_DeviceMotionEvent_acceleration.ql +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @id prebid/devicemotionevent-acceleration - * @name Potential access to DeviceMotionEvent.acceleration - * @kind problem - * @problem.severity warning - * @description Finds uses of acceleration - */ - -// this file is autogenerated, see fingerprintApis.mjs - -import prebid -from PropRef api -where - api.getPropertyName() = "acceleration" -select api, "acceleration is an indicator of fingerprinting, weighed 57.38 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql b/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql deleted file mode 100644 index 6be3f9ff0e5..00000000000 --- a/.github/codeql/queries/autogen_DeviceMotionEvent_accelerationIncludingGravity.ql +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @id prebid/devicemotionevent-accelerationincludinggravity - * @name Potential access to DeviceMotionEvent.accelerationIncludingGravity - * @kind problem - * @problem.severity warning - * @description Finds uses of accelerationIncludingGravity - */ - -// this file is autogenerated, see fingerprintApis.mjs - -import prebid -from PropRef api -where - api.getPropertyName() = "accelerationIncludingGravity" -select api, "accelerationIncludingGravity is an indicator of fingerprinting, weighed 148.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql b/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql deleted file mode 100644 index 6d56771914c..00000000000 --- a/.github/codeql/queries/autogen_DeviceMotionEvent_rotationRate.ql +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @id prebid/devicemotionevent-rotationrate - * @name Potential access to DeviceMotionEvent.rotationRate - * @kind problem - * @problem.severity warning - * @description Finds uses of rotationRate - */ - -// this file is autogenerated, see fingerprintApis.mjs - -import prebid -from PropRef api -where - api.getPropertyName() = "rotationRate" -select api, "rotationRate is an indicator of fingerprinting, weighed 57.43 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope.ql b/.github/codeql/queries/autogen_Gyroscope.ql index 87a96e8aa94..b2b4d7ce656 100644 --- a/.github/codeql/queries/autogen_Gyroscope.ql +++ b/.github/codeql/queries/autogen_Gyroscope.ql @@ -11,5 +11,5 @@ import prebid from SourceNode api where - api = instantiationOf("Gyroscope") -select api, "Gyroscope is an indicator of fingerprinting, weighed 160.04 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" + api = callTo("Gyroscope") +select api, "Gyroscope is an indicator of fingerprinting, weighed 142.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope_x.ql b/.github/codeql/queries/autogen_Gyroscope_x.ql index 87fa3db54d5..bdcc5dfd85d 100644 --- a/.github/codeql/queries/autogen_Gyroscope_x.ql +++ b/.github/codeql/queries/autogen_Gyroscope_x.ql @@ -11,6 +11,6 @@ import prebid from SourceNode inst, SourceNode api where - inst = instantiationOf("Gyroscope") and + inst = callTo("Gyroscope") and api = inst.getAPropertyRead("x") -select api, "x is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "x is an indicator of fingerprinting, weighed 5043.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope_y.ql b/.github/codeql/queries/autogen_Gyroscope_y.ql index 7586e56ba4f..e3b5278a922 100644 --- a/.github/codeql/queries/autogen_Gyroscope_y.ql +++ b/.github/codeql/queries/autogen_Gyroscope_y.ql @@ -11,6 +11,6 @@ import prebid from SourceNode inst, SourceNode api where - inst = instantiationOf("Gyroscope") and + inst = callTo("Gyroscope") and api = inst.getAPropertyRead("y") -select api, "y is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "y is an indicator of fingerprinting, weighed 5043.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Gyroscope_z.ql b/.github/codeql/queries/autogen_Gyroscope_z.ql index a772dc236d6..585381c340f 100644 --- a/.github/codeql/queries/autogen_Gyroscope_z.ql +++ b/.github/codeql/queries/autogen_Gyroscope_z.ql @@ -11,6 +11,6 @@ import prebid from SourceNode inst, SourceNode api where - inst = instantiationOf("Gyroscope") and + inst = callTo("Gyroscope") and api = inst.getAPropertyRead("z") -select api, "z is an indicator of fingerprinting, weighed 4458.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "z is an indicator of fingerprinting, weighed 5043.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Intl-DateTimeFormat_resolvedOptions.ql b/.github/codeql/queries/autogen_Intl-DateTimeFormat_resolvedOptions.ql new file mode 100644 index 00000000000..6f87409989a --- /dev/null +++ b/.github/codeql/queries/autogen_Intl-DateTimeFormat_resolvedOptions.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/intl-datetimeformat-resolvedoptions + * @name Access to Intl-DateTimeFormat.resolvedOptions + * @kind problem + * @problem.severity warning + * @description Finds uses of Intl-DateTimeFormat.resolvedOptions + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import prebid +from SourceNode inst, SourceNode api +where + inst = callTo("Intl", "DateTimeFormat") and + api = inst.getAPropertyRead("resolvedOptions") +select api, "resolvedOptions is an indicator of fingerprinting, weighed 17.99 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_Notification_permission.ql b/.github/codeql/queries/autogen_Notification_permission.ql index ed009fae21b..9a57c280236 100644 --- a/.github/codeql/queries/autogen_Notification_permission.ql +++ b/.github/codeql/queries/autogen_Notification_permission.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("Notification") and api = prop.getAPropertyRead("permission") -select api, "permission is an indicator of fingerprinting, weighed 22.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "permission is an indicator of fingerprinting, weighed 22.02 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_OfflineAudioContext.ql b/.github/codeql/queries/autogen_OfflineAudioContext.ql index a567e8b6434..bf102fcab1d 100644 --- a/.github/codeql/queries/autogen_OfflineAudioContext.ql +++ b/.github/codeql/queries/autogen_OfflineAudioContext.ql @@ -11,5 +11,5 @@ import prebid from SourceNode api where - api = instantiationOf("OfflineAudioContext") -select api, "OfflineAudioContext is an indicator of fingerprinting, weighed 1120.16 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" + api = callTo("OfflineAudioContext") +select api, "OfflineAudioContext is an indicator of fingerprinting, weighed 1135.77 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_RTCPeerConnection.ql b/.github/codeql/queries/autogen_RTCPeerConnection.ql index ebbfa8f2e6f..d70f0311d10 100644 --- a/.github/codeql/queries/autogen_RTCPeerConnection.ql +++ b/.github/codeql/queries/autogen_RTCPeerConnection.ql @@ -11,5 +11,5 @@ import prebid from SourceNode api where - api = instantiationOf("RTCPeerConnection") -select api, "RTCPeerConnection is an indicator of fingerprinting, weighed 47.69 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" + api = callTo("RTCPeerConnection") +select api, "RTCPeerConnection is an indicator of fingerprinting, weighed 49.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_SharedWorker.ql b/.github/codeql/queries/autogen_SharedWorker.ql index 34c9303f368..c9970b019b0 100644 --- a/.github/codeql/queries/autogen_SharedWorker.ql +++ b/.github/codeql/queries/autogen_SharedWorker.ql @@ -11,5 +11,5 @@ import prebid from SourceNode api where - api = instantiationOf("SharedWorker") -select api, "SharedWorker is an indicator of fingerprinting, weighed 93.37 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" + api = callTo("SharedWorker") +select api, "SharedWorker is an indicator of fingerprinting, weighed 78.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_devicemotion_acceleration.ql b/.github/codeql/queries/autogen_domEvent_devicemotion_acceleration.ql new file mode 100644 index 00000000000..8dd10e55293 --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_devicemotion_acceleration.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-devicemotion-acceleration + * @name Access to domEvent acceleration (devicemotion) + * @kind problem + * @problem.severity warning + * @description Finds uses of acceleration on domEvent (devicemotion) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("devicemotion") and + api = target.getAPropertyRead("acceleration") +select api, "acceleration is an indicator of fingerprinting, weighed 58.05 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_devicemotion_accelerationIncludingGravity.ql b/.github/codeql/queries/autogen_domEvent_devicemotion_accelerationIncludingGravity.ql new file mode 100644 index 00000000000..a71a00256aa --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_devicemotion_accelerationIncludingGravity.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-devicemotion-accelerationincludinggravity + * @name Access to domEvent accelerationIncludingGravity (devicemotion) + * @kind problem + * @problem.severity warning + * @description Finds uses of accelerationIncludingGravity on domEvent (devicemotion) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("devicemotion") and + api = target.getAPropertyRead("accelerationIncludingGravity") +select api, "accelerationIncludingGravity is an indicator of fingerprinting, weighed 149.23 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_devicemotion_rotationRate.ql b/.github/codeql/queries/autogen_domEvent_devicemotion_rotationRate.ql new file mode 100644 index 00000000000..13784ff4cf9 --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_devicemotion_rotationRate.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-devicemotion-rotationrate + * @name Access to domEvent rotationRate (devicemotion) + * @kind problem + * @problem.severity warning + * @description Finds uses of rotationRate on domEvent (devicemotion) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("devicemotion") and + api = target.getAPropertyRead("rotationRate") +select api, "rotationRate is an indicator of fingerprinting, weighed 57.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_deviceorientation_absolute.ql b/.github/codeql/queries/autogen_domEvent_deviceorientation_absolute.ql new file mode 100644 index 00000000000..72fd1946e5e --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_deviceorientation_absolute.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-deviceorientation-absolute + * @name Access to domEvent absolute (deviceorientation) + * @kind problem + * @problem.severity warning + * @description Finds uses of absolute on domEvent (deviceorientation) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("deviceorientation") and + api = target.getAPropertyRead("absolute") +select api, "absolute is an indicator of fingerprinting, weighed 387.12 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_deviceorientation_alpha.ql b/.github/codeql/queries/autogen_domEvent_deviceorientation_alpha.ql new file mode 100644 index 00000000000..7be9a5743ee --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_deviceorientation_alpha.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-deviceorientation-alpha + * @name Access to domEvent alpha (deviceorientation) + * @kind problem + * @problem.severity warning + * @description Finds uses of alpha on domEvent (deviceorientation) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("deviceorientation") and + api = target.getAPropertyRead("alpha") +select api, "alpha is an indicator of fingerprinting, weighed 366.53 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_deviceorientation_beta.ql b/.github/codeql/queries/autogen_domEvent_deviceorientation_beta.ql new file mode 100644 index 00000000000..96262dd734f --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_deviceorientation_beta.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-deviceorientation-beta + * @name Access to domEvent beta (deviceorientation) + * @kind problem + * @problem.severity warning + * @description Finds uses of beta on domEvent (deviceorientation) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("deviceorientation") and + api = target.getAPropertyRead("beta") +select api, "beta is an indicator of fingerprinting, weighed 1075.3 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_domEvent_deviceorientation_gamma.ql b/.github/codeql/queries/autogen_domEvent_deviceorientation_gamma.ql new file mode 100644 index 00000000000..022b2007346 --- /dev/null +++ b/.github/codeql/queries/autogen_domEvent_deviceorientation_gamma.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/domevent-deviceorientation-gamma + * @name Access to domEvent gamma (deviceorientation) + * @kind problem + * @problem.severity warning + * @description Finds uses of gamma on domEvent (deviceorientation) + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import domEvent +from SourceNode target, SourceNode api +where + target = domEvent("deviceorientation") and + api = target.getAPropertyRead("gamma") +select api, "gamma is an indicator of fingerprinting, weighed 395.62 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_appCodeName.ql b/.github/codeql/queries/autogen_navigator_appCodeName.ql index fecacea0561..3fb11e10129 100644 --- a/.github/codeql/queries/autogen_navigator_appCodeName.ql +++ b/.github/codeql/queries/autogen_navigator_appCodeName.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("appCodeName") -select api, "appCodeName is an indicator of fingerprinting, weighed 145.05 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "appCodeName is an indicator of fingerprinting, weighed 143.58 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_cookieEnabled.ql b/.github/codeql/queries/autogen_navigator_cookieEnabled.ql index 9d3938dd617..fcfd94f51bd 100644 --- a/.github/codeql/queries/autogen_navigator_cookieEnabled.ql +++ b/.github/codeql/queries/autogen_navigator_cookieEnabled.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("cookieEnabled") -select api, "cookieEnabled is an indicator of fingerprinting, weighed 15.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "cookieEnabled is an indicator of fingerprinting, weighed 15.3 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_deviceMemory.ql b/.github/codeql/queries/autogen_navigator_deviceMemory.ql index 48e46ee2319..951b6b6f51f 100644 --- a/.github/codeql/queries/autogen_navigator_deviceMemory.ql +++ b/.github/codeql/queries/autogen_navigator_deviceMemory.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("deviceMemory") -select api, "deviceMemory is an indicator of fingerprinting, weighed 70.5 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "deviceMemory is an indicator of fingerprinting, weighed 75.06 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_getBattery.ql b/.github/codeql/queries/autogen_navigator_getBattery.ql index dff44f0f10a..5501e2eaf63 100644 --- a/.github/codeql/queries/autogen_navigator_getBattery.ql +++ b/.github/codeql/queries/autogen_navigator_getBattery.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("getBattery") -select api, "getBattery is an indicator of fingerprinting, weighed 115.54 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getBattery is an indicator of fingerprinting, weighed 114.16 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_getGamepads.ql b/.github/codeql/queries/autogen_navigator_getGamepads.ql index e9f436f2846..f0423d45b6a 100644 --- a/.github/codeql/queries/autogen_navigator_getGamepads.ql +++ b/.github/codeql/queries/autogen_navigator_getGamepads.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("getGamepads") -select api, "getGamepads is an indicator of fingerprinting, weighed 211.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getGamepads is an indicator of fingerprinting, weighed 235.72 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql b/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql index 21f70cbeaf3..d3f1c3dabbd 100644 --- a/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql +++ b/.github/codeql/queries/autogen_navigator_hardwareConcurrency.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("hardwareConcurrency") -select api, "hardwareConcurrency is an indicator of fingerprinting, weighed 63.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "hardwareConcurrency is an indicator of fingerprinting, weighed 67.85 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_keyboard.ql b/.github/codeql/queries/autogen_navigator_keyboard.ql index 0083fa4d66c..5f0af11709b 100644 --- a/.github/codeql/queries/autogen_navigator_keyboard.ql +++ b/.github/codeql/queries/autogen_navigator_keyboard.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("keyboard") -select api, "keyboard is an indicator of fingerprinting, weighed 938.38 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "keyboard is an indicator of fingerprinting, weighed 957.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql b/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql index 3b85315c4c4..a52e0574116 100644 --- a/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql +++ b/.github/codeql/queries/autogen_navigator_mediaCapabilities.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("mediaCapabilities") -select api, "mediaCapabilities is an indicator of fingerprinting, weighed 123.84 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "mediaCapabilities is an indicator of fingerprinting, weighed 126.07 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_mediaDevices.ql b/.github/codeql/queries/autogen_navigator_mediaDevices.ql index ed7e930a68f..aa3f356c149 100644 --- a/.github/codeql/queries/autogen_navigator_mediaDevices.ql +++ b/.github/codeql/queries/autogen_navigator_mediaDevices.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("mediaDevices") -select api, "mediaDevices is an indicator of fingerprinting, weighed 122.39 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "mediaDevices is an indicator of fingerprinting, weighed 121.74 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_onLine.ql b/.github/codeql/queries/autogen_navigator_onLine.ql index 5997eef8852..54737af004c 100644 --- a/.github/codeql/queries/autogen_navigator_onLine.ql +++ b/.github/codeql/queries/autogen_navigator_onLine.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("onLine") -select api, "onLine is an indicator of fingerprinting, weighed 20.21 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "onLine is an indicator of fingerprinting, weighed 19.76 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_permissions.ql b/.github/codeql/queries/autogen_navigator_permissions.ql index 55fb1eb60d5..c87c3b3b836 100644 --- a/.github/codeql/queries/autogen_navigator_permissions.ql +++ b/.github/codeql/queries/autogen_navigator_permissions.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("permissions") -select api, "permissions is an indicator of fingerprinting, weighed 83.98 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "permissions is an indicator of fingerprinting, weighed 66.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_productSub.ql b/.github/codeql/queries/autogen_navigator_productSub.ql index e323428f028..b813c1fa6df 100644 --- a/.github/codeql/queries/autogen_navigator_productSub.ql +++ b/.github/codeql/queries/autogen_navigator_productSub.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("productSub") -select api, "productSub is an indicator of fingerprinting, weighed 483.14 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "productSub is an indicator of fingerprinting, weighed 482.29 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql b/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql index c7101c81361..c6415a4eefa 100644 --- a/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql +++ b/.github/codeql/queries/autogen_navigator_requestMediaKeySystemAccess.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("requestMediaKeySystemAccess") -select api, "requestMediaKeySystemAccess is an indicator of fingerprinting, weighed 16.48 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "requestMediaKeySystemAccess is an indicator of fingerprinting, weighed 17.34 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_storage.ql b/.github/codeql/queries/autogen_navigator_storage.ql index b73d9231393..ad3132f8ed8 100644 --- a/.github/codeql/queries/autogen_navigator_storage.ql +++ b/.github/codeql/queries/autogen_navigator_storage.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("storage") -select api, "storage is an indicator of fingerprinting, weighed 150.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "storage is an indicator of fingerprinting, weighed 151.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_vendorSub.ql b/.github/codeql/queries/autogen_navigator_vendorSub.ql index 64d834523a8..5497090039b 100644 --- a/.github/codeql/queries/autogen_navigator_vendorSub.ql +++ b/.github/codeql/queries/autogen_navigator_vendorSub.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("vendorSub") -select api, "vendorSub is an indicator of fingerprinting, weighed 1670.4 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "vendorSub is an indicator of fingerprinting, weighed 1791.96 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_webdriver.ql b/.github/codeql/queries/autogen_navigator_webdriver.ql index 80684d09387..dcbb7cc32d1 100644 --- a/.github/codeql/queries/autogen_navigator_webdriver.ql +++ b/.github/codeql/queries/autogen_navigator_webdriver.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("webdriver") -select api, "webdriver is an indicator of fingerprinting, weighed 32.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "webdriver is an indicator of fingerprinting, weighed 31.25 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql b/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql index 5938592ba38..be95d29caaa 100644 --- a/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql +++ b/.github/codeql/queries/autogen_navigator_webkitPersistentStorage.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("webkitPersistentStorage") -select api, "webkitPersistentStorage is an indicator of fingerprinting, weighed 129.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "webkitPersistentStorage is an indicator of fingerprinting, weighed 150.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql b/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql index 7d3c990b5fe..ceac2f520d9 100644 --- a/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql +++ b/.github/codeql/queries/autogen_navigator_webkitTemporaryStorage.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("navigator") and api = prop.getAPropertyRead("webkitTemporaryStorage") -select api, "webkitTemporaryStorage is an indicator of fingerprinting, weighed 39.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "webkitTemporaryStorage is an indicator of fingerprinting, weighed 40.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availHeight.ql b/.github/codeql/queries/autogen_screen_availHeight.ql index a589b2867bd..b6f62682608 100644 --- a/.github/codeql/queries/autogen_screen_availHeight.ql +++ b/.github/codeql/queries/autogen_screen_availHeight.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availHeight") -select api, "availHeight is an indicator of fingerprinting, weighed 72.86 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availHeight is an indicator of fingerprinting, weighed 70.68 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availLeft.ql b/.github/codeql/queries/autogen_screen_availLeft.ql index 5b258b3e4bc..8db95e48ea0 100644 --- a/.github/codeql/queries/autogen_screen_availLeft.ql +++ b/.github/codeql/queries/autogen_screen_availLeft.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availLeft") -select api, "availLeft is an indicator of fingerprinting, weighed 617.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availLeft is an indicator of fingerprinting, weighed 547.54 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availTop.ql b/.github/codeql/queries/autogen_screen_availTop.ql index 8487c3ba39b..73b10419146 100644 --- a/.github/codeql/queries/autogen_screen_availTop.ql +++ b/.github/codeql/queries/autogen_screen_availTop.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availTop") -select api, "availTop is an indicator of fingerprinting, weighed 1374.93 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availTop is an indicator of fingerprinting, weighed 1240.09 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_availWidth.ql b/.github/codeql/queries/autogen_screen_availWidth.ql index 6e109fd9abc..7c0c984ff72 100644 --- a/.github/codeql/queries/autogen_screen_availWidth.ql +++ b/.github/codeql/queries/autogen_screen_availWidth.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("availWidth") -select api, "availWidth is an indicator of fingerprinting, weighed 67.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "availWidth is an indicator of fingerprinting, weighed 65.56 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_colorDepth.ql b/.github/codeql/queries/autogen_screen_colorDepth.ql index 577fcdc7a3f..ba66e9d52d5 100644 --- a/.github/codeql/queries/autogen_screen_colorDepth.ql +++ b/.github/codeql/queries/autogen_screen_colorDepth.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("colorDepth") -select api, "colorDepth is an indicator of fingerprinting, weighed 34.52 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "colorDepth is an indicator of fingerprinting, weighed 34.27 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_orientation.ql b/.github/codeql/queries/autogen_screen_orientation.ql index 0e4e800f600..4d4d3c2652b 100644 --- a/.github/codeql/queries/autogen_screen_orientation.ql +++ b/.github/codeql/queries/autogen_screen_orientation.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("orientation") -select api, "orientation is an indicator of fingerprinting, weighed 44.76 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "orientation is an indicator of fingerprinting, weighed 35.82 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_screen_pixelDepth.ql b/.github/codeql/queries/autogen_screen_pixelDepth.ql index d5444006a60..40f9cfedfd8 100644 --- a/.github/codeql/queries/autogen_screen_pixelDepth.ql +++ b/.github/codeql/queries/autogen_screen_pixelDepth.ql @@ -13,4 +13,4 @@ from SourceNode prop, SourceNode api where prop = windowPropertyRead("screen") and api = prop.getAPropertyRead("pixelDepth") -select api, "pixelDepth is an indicator of fingerprinting, weighed 37.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "pixelDepth is an indicator of fingerprinting, weighed 37.72 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_sensor__start.ql b/.github/codeql/queries/autogen_sensor__start.ql new file mode 100644 index 00000000000..34c514fb33a --- /dev/null +++ b/.github/codeql/queries/autogen_sensor__start.ql @@ -0,0 +1,16 @@ +/** + * @id prebid/sensor-start + * @name Access to sensor start + * @kind problem + * @problem.severity warning + * @description Finds uses of start on sensor + */ + +// this file is autogenerated, see fingerprintApis.mjs + +import sensor +from SourceNode target, SourceNode api +where + target = sensor() and + api = target.getAPropertyRead("start") +select api, "start is an indicator of fingerprinting, weighed 123.75 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql index 98f04b45434..9cf09d54753 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getContextAttributes.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getContextAttributes") -select api, "getContextAttributes is an indicator of fingerprinting, weighed 187.76 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getContextAttributes is an indicator of fingerprinting, weighed 187.09 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql index f36b5e2ea77..00e863e36a5 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getExtension.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getExtension") -select api, "getExtension is an indicator of fingerprinting, weighed 44.33 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getExtension is an indicator of fingerprinting, weighed 44.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql index 3eea9979ed2..106d0e8b43c 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getParameter.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getParameter") -select api, "getParameter is an indicator of fingerprinting, weighed 41.59 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getParameter is an indicator of fingerprinting, weighed 41.44 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql index 38f8ff15608..feb0e1413c5 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getShaderPrecisionFormat.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getShaderPrecisionFormat") -select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 103.05 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 108.95 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql index 240d70f969e..5015af5b8a0 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_getSupportedExtensions.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("getSupportedExtensions") -select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 466.18 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 535.91 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql b/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql index 04856b3d38a..48b34437c2b 100644 --- a/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql +++ b/.github/codeql/queries/autogen_webgl2_RenderingContext_readPixels.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl2") and api = invocation.getAPropertyRead("readPixels") -select api, "readPixels is an indicator of fingerprinting, weighed 66.74 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "readPixels is an indicator of fingerprinting, weighed 68.7 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql index f0fdd734b1a..08d426facd3 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getContextAttributes.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getContextAttributes") -select api, "getContextAttributes is an indicator of fingerprinting, weighed 2006.6 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getContextAttributes is an indicator of fingerprinting, weighed 1404.12 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql index d9d840d4ad8..2b2e9497722 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getExtension.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getExtension") -select api, "getExtension is an indicator of fingerprinting, weighed 21.18 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getExtension is an indicator of fingerprinting, weighed 20.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql index a86709d09d5..28ae78c590d 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getParameter.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getParameter") -select api, "getParameter is an indicator of fingerprinting, weighed 23.51 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getParameter is an indicator of fingerprinting, weighed 22.92 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql index 5d51a714c60..418cd3f048e 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getShaderPrecisionFormat.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getShaderPrecisionFormat") -select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 688.97 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getShaderPrecisionFormat is an indicator of fingerprinting, weighed 632.69 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql index d8cb422f67e..06dc2b518ae 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_getSupportedExtensions.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("getSupportedExtensions") -select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 1008.67 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "getSupportedExtensions is an indicator of fingerprinting, weighed 968.57 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql b/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql index efdd39edf6d..1cac8792567 100644 --- a/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql +++ b/.github/codeql/queries/autogen_webgl_RenderingContext_readPixels.ql @@ -14,4 +14,4 @@ where invocation.getCalleeName() = "getContext" and invocation.getArgument(0).mayHaveStringValue("webgl") and api = invocation.getAPropertyRead("readPixels") -select api, "readPixels is an indicator of fingerprinting, weighed 15.25 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "readPixels is an indicator of fingerprinting, weighed 21.25 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_devicePixelRatio.ql b/.github/codeql/queries/autogen_window_devicePixelRatio.ql index e4cf78b5ecb..87a652ffc48 100644 --- a/.github/codeql/queries/autogen_window_devicePixelRatio.ql +++ b/.github/codeql/queries/autogen_window_devicePixelRatio.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("devicePixelRatio") -select api, "devicePixelRatio is an indicator of fingerprinting, weighed 19.19 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "devicePixelRatio is an indicator of fingerprinting, weighed 19.42 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_indexedDB.ql b/.github/codeql/queries/autogen_window_indexedDB.ql index db33f272305..6a64200af86 100644 --- a/.github/codeql/queries/autogen_window_indexedDB.ql +++ b/.github/codeql/queries/autogen_window_indexedDB.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("indexedDB") -select api, "indexedDB is an indicator of fingerprinting, weighed 17.63 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "indexedDB is an indicator of fingerprinting, weighed 17.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_openDatabase.ql b/.github/codeql/queries/autogen_window_openDatabase.ql index a6f69d6f020..e701d88d2bb 100644 --- a/.github/codeql/queries/autogen_window_openDatabase.ql +++ b/.github/codeql/queries/autogen_window_openDatabase.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("openDatabase") -select api, "openDatabase is an indicator of fingerprinting, weighed 137.87 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "openDatabase is an indicator of fingerprinting, weighed 143.97 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_outerHeight.ql b/.github/codeql/queries/autogen_window_outerHeight.ql index 16acef61f0a..ea8f33e20e5 100644 --- a/.github/codeql/queries/autogen_window_outerHeight.ql +++ b/.github/codeql/queries/autogen_window_outerHeight.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("outerHeight") -select api, "outerHeight is an indicator of fingerprinting, weighed 181.21 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "outerHeight is an indicator of fingerprinting, weighed 183.94 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_outerWidth.ql b/.github/codeql/queries/autogen_window_outerWidth.ql index af08cf7af41..f6bf150613d 100644 --- a/.github/codeql/queries/autogen_window_outerWidth.ql +++ b/.github/codeql/queries/autogen_window_outerWidth.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("outerWidth") -select api, "outerWidth is an indicator of fingerprinting, weighed 105.02 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "outerWidth is an indicator of fingerprinting, weighed 102.66 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenLeft.ql b/.github/codeql/queries/autogen_window_screenLeft.ql index e3d8ac11c01..aebed23a545 100644 --- a/.github/codeql/queries/autogen_window_screenLeft.ql +++ b/.github/codeql/queries/autogen_window_screenLeft.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenLeft") -select api, "screenLeft is an indicator of fingerprinting, weighed 332.08 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenLeft is an indicator of fingerprinting, weighed 315.55 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenTop.ql b/.github/codeql/queries/autogen_window_screenTop.ql index 5c13951385e..caab63c6b64 100644 --- a/.github/codeql/queries/autogen_window_screenTop.ql +++ b/.github/codeql/queries/autogen_window_screenTop.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenTop") -select api, "screenTop is an indicator of fingerprinting, weighed 329.11 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenTop is an indicator of fingerprinting, weighed 313.8 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenX.ql b/.github/codeql/queries/autogen_window_screenX.ql index cf5d788ce45..ee5555ee11c 100644 --- a/.github/codeql/queries/autogen_window_screenX.ql +++ b/.github/codeql/queries/autogen_window_screenX.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenX") -select api, "screenX is an indicator of fingerprinting, weighed 301.79 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenX is an indicator of fingerprinting, weighed 319.5 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/autogen_window_screenY.ql b/.github/codeql/queries/autogen_window_screenY.ql index aecf595a15b..f9a006405e8 100644 --- a/.github/codeql/queries/autogen_window_screenY.ql +++ b/.github/codeql/queries/autogen_window_screenY.ql @@ -12,4 +12,4 @@ import prebid from SourceNode api where api = windowPropertyRead("screenY") -select api, "screenY is an indicator of fingerprinting, weighed 285.97 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" +select api, "screenY is an indicator of fingerprinting, weighed 303.5 in https://raw.githubusercontent.com/duckduckgo/tracker-radar/refs/heads/main/build-data/generated/api_fingerprint_weights.json" diff --git a/.github/codeql/queries/domEvent.qll b/.github/codeql/queries/domEvent.qll new file mode 100644 index 00000000000..d55111ec950 --- /dev/null +++ b/.github/codeql/queries/domEvent.qll @@ -0,0 +1,34 @@ +import prebid + + +SourceNode domEventListener(TypeTracker t, string event) { + t.start() and + ( + exists(MethodCallNode addEventListener | + addEventListener.getMethodName() = "addEventListener" and + addEventListener.getArgument(0).mayHaveStringValue(event) and + result = addEventListener.getArgument(1).(FunctionNode).getParameter(0) + ) + ) + or + exists(TypeTracker t2 | + result = domEventListener(t2, event).track(t2, t) + ) +} + +SourceNode domEventSetter(TypeTracker t, string eventSetter) { + t.start() and + exists(PropWrite write | + write.getPropertyName() = eventSetter and + result = write.getRhs().(FunctionNode).getParameter(0) + ) or + exists(TypeTracker t2 | + result = domEventSetter(t2, eventSetter).track(t2, t) + ) +} + +bindingset[event] +SourceNode domEvent(string event) { + result = domEventListener(TypeTracker::end(), event) or + result = domEventSetter(TypeTracker::end(), "on" + event.toLowerCase()) +} diff --git a/.github/codeql/queries/prebid.qll b/.github/codeql/queries/prebid.qll index 0bcf68cfba0..afa52edf072 100644 --- a/.github/codeql/queries/prebid.qll +++ b/.github/codeql/queries/prebid.qll @@ -1,20 +1,39 @@ import javascript import DataFlow +SourceNode otherWindow(TypeTracker t) { + t.start() and ( + result = globalVarRef("window") or + result = globalVarRef("top") or + result = globalVarRef("self") or + result = globalVarRef("parent") or + result = globalVarRef("frames").getAPropertyRead() or + result = DOM::documentRef().getAPropertyRead("defaultView") + ) or + exists(TypeTracker t2 | + result = otherWindow(t2).track(t2, t) + ) +} + SourceNode otherWindow() { - result = globalVarRef("top") or - result = globalVarRef("self") or - result = globalVarRef("parent") or - result = globalVarRef("frames").getAPropertyRead() or - result = DOM::documentRef().getAPropertyRead("defaultView") + result = otherWindow(TypeTracker::end()) +} + +SourceNode connectedWindow(TypeTracker t, SourceNode win) { + t.start() and ( + result = win.getAPropertyRead("self") or + result = win.getAPropertyRead("top") or + result = win.getAPropertyRead("parent") or + result = win.getAPropertyRead("frames").getAPropertyRead() or + result = win.getAPropertyRead("document").getAPropertyRead("defaultView") + ) or + exists(TypeTracker t2 | + result = connectedWindow(t2, win).track(t2, t) + ) } SourceNode connectedWindow(SourceNode win) { - result = win.getAPropertyRead("self") or - result = win.getAPropertyRead("top") or - result = win.getAPropertyRead("parent") or - result = win.getAPropertyRead("frames").getAPropertyRead() or - result = win.getAPropertyRead("document").getAPropertyRead("defaultView") + result = connectedWindow(TypeTracker::end(), win) } SourceNode relatedWindow(SourceNode win) { @@ -27,15 +46,39 @@ SourceNode anyWindow() { result = relatedWindow(otherWindow()) } +SourceNode windowPropertyRead(TypeTracker t, string prop) { + t.start() and ( + result = globalVarRef(prop) or + result = anyWindow().getAPropertyRead(prop) + ) or + exists(TypeTracker t2 | + result = windowPropertyRead(t2, prop).track(t2, t) + ) +} + /* Matches uses of property `prop` done on any window object. */ SourceNode windowPropertyRead(string prop) { - result = globalVarRef(prop) or - result = anyWindow().getAPropertyRead(prop) + result = windowPropertyRead(TypeTracker::end(), prop) } -SourceNode instantiationOf(string ctr) { - result = windowPropertyRead(ctr).getAnInstantiation() +SourceNode callTo(string globalVar) { + exists(SourceNode fn | + fn = windowPropertyRead(globalVar) and + ( + result = fn.getAnInstantiation() or + result = fn.getAnInvocation() + ) + ) } +SourceNode callTo(string globalVar, string name) { + exists(SourceNode fn | + fn = windowPropertyRead(globalVar).getAPropertyRead(name) and + ( + result = fn.getAnInstantiation() or + result = fn.getAnInvocation() + ) + ) +} diff --git a/.github/codeql/queries/sensor.qll b/.github/codeql/queries/sensor.qll new file mode 100644 index 00000000000..d2d56606cd6 --- /dev/null +++ b/.github/codeql/queries/sensor.qll @@ -0,0 +1,22 @@ +import prebid + +SourceNode sensor(TypeTracker t) { + t.start() and exists(string variant | + variant in [ + "Gyroscope", + "Accelerometer", + "LinearAccelerationSensor", + "AbsoluteOrientationSensor", + "RelativeOrientationSensor", + "Magnetometer", + "AmbientLightSensor" + ] and + result = callTo(variant) + ) or exists(TypeTracker t2 | + result = sensor(t2).track(t2, t) + ) +} + +SourceNode sensor() { + result = sensor(TypeTracker::end()) +} diff --git a/fingerprintApis.mjs b/fingerprintApis.mjs index 4694042923b..f8c964cce11 100644 --- a/fingerprintApis.mjs +++ b/fingerprintApis.mjs @@ -76,13 +76,15 @@ function globalConstructor(weight, ctr) { query: `import prebid from SourceNode api where - api = instantiationOf("${ctr}") + api = callTo("${ctr}") select api, ${message(weight, ctr)}` }) ] } -function globalConstructorProperty(weight, ctr, api) { +function globalConstructorProperty(weight, ...args) { + const api = args.pop(); + const ctr = args.join('-'); return [ `${ctr}_${api}`, QUERY_FILE_TPL({ @@ -92,28 +94,13 @@ function globalConstructorProperty(weight, ctr, api) { query: `import prebid from SourceNode inst, SourceNode api where - inst = instantiationOf("${ctr}") and + inst = callTo(${args.map(arg => `"${arg}"`).join(', ')}) and api = inst.getAPropertyRead("${api}") select api, ${message(weight, api)}` }) ] } -function simplePropertyMatch(weight, target, prop) { - return [ - `${target}_${prop}`, - QUERY_FILE_TPL({ - id: `${target}-${prop}`.toLowerCase(), - name: `Potential access to ${target}.${prop}`, - description: `Finds uses of ${prop}`, - query: `import prebid -from PropRef api -where - api.getPropertyName() = "${prop}" -select api, ${message(weight, prop)}` - }) - ] -} function glContextMatcher(contextType) { return function(weight, api) { return [ @@ -134,6 +121,46 @@ select api, ${message(weight, api)}` } } +function eventPropertyMatcher(event) { + return function(weight, api) { + return [ + `${event}_${api}`, + QUERY_FILE_TPL({ + id: `${event}-${api}`.toLowerCase(), + name: `Access to ${event}.${api}`, + description: `Finds uses of ${api} on ${event} events`, + query: `import event +from SourceNode event, SourceNode api +where + event = domEvent("${event}") and + api = event.getAPropertyRead("${api}") +select api, ${message(weight, api)}` + }) + ] + } +} + +function adHocPropertyMatcher(type, ...args) { + const argDesc = args.length > 0 ? ` (${args.join(', ')})` : ''; + const argId = args.length > 0 ? '-' + args.join('-') : ''; + return function(weight, api) { + return [ + `${type}_${args.join('_')}_${api}`, + QUERY_FILE_TPL({ + id: `${type}${argId}-${api}`.toLowerCase(), + name: `Access to ${type} ${api}${argDesc}`, + description: `Finds uses of ${api} on ${type}${argDesc}`, + query: `import ${type} +from SourceNode target, SourceNode api +where + target = ${type}(${args.map(arg => `"${arg}"`).join(', ')}) and + api = target.getAPropertyRead("${api}") +select api, ${message(weight, api)}` + }) + ] + } +} + const API_MATCHERS = [ [/^([^.]+)\.prototype.constructor$/, globalConstructor], [/^Screen\.prototype\.(.*)$/, globalProp('screen')], @@ -141,10 +168,13 @@ const API_MATCHERS = [ [/^window\.(.*)$/, windowProp], [/^Navigator.prototype\.(.*)$/, globalProp('navigator')], [/^(Date|Gyroscope)\.prototype\.(.*)$/, globalConstructorProperty], - [/^(DeviceMotionEvent)\.prototype\.(.*)$/, simplePropertyMatch], + [/^(Intl)\.(DateTimeFormat)\.prototype\.(.*)$/, globalConstructorProperty], + [/^DeviceMotionEvent\.prototype\.(.*)$/, adHocPropertyMatcher("domEvent", "devicemotion")], + [/^DeviceOrientationEvent\.prototype\.(.*)$/, adHocPropertyMatcher("domEvent", "deviceorientation")], [/^WebGLRenderingContext\.prototype\.(.*)$/, glContextMatcher('webgl')], [/^WebGL2RenderingContext\.prototype\.(.*)$/, glContextMatcher('webgl2')], [/^CanvasRenderingContext2D\.prototype\.(.*)$/, glContextMatcher('2d')], + [/^Sensor.prototype\.(.*)$/, adHocPropertyMatcher('sensor')], ]; async function generateQueries() { From 65e6c0bc5bad095d51cbb75637badebb2eabb369 Mon Sep 17 00:00:00 2001 From: pm-nitin-shirsat <107102698+pm-nitin-shirsat@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:20:44 +0530 Subject: [PATCH 054/147] Consent Management : reset functionality to properly disable TCF/GPP modules (#13989) * Prebid Core: Cosnent Handler reset functionality * Prebid Consent Management: Add reset * Consent Management Reset: Remove add event listener if it is listening already * Consent management changes working * Consent Management: add enabled flag before enabling the module. Provided backward compatibility * Consent Management: logInfo to logWarn * Consent Manegement reset fix * Add gdpr test cases * Move the cmp event listener removal functions to libraries --------- Co-authored-by: Patrick McCann Co-authored-by: Demetrio Girardi Co-authored-by: Patrick McCann --- libraries/cmp/cmpEventUtils.ts | 121 +++++++ libraries/consentManagement/cmUtils.ts | 28 ++ modules/consentManagementGpp.ts | 27 +- modules/consentManagementTcf.ts | 26 +- src/consentHandler.ts | 24 +- test/spec/libraries/cmUtils_spec.js | 124 ++++++- test/spec/libraries/cmp/cmpEventUtils_spec.js | 330 ++++++++++++++++++ test/spec/modules/consentManagement_spec.js | 121 ++++++- 8 files changed, 791 insertions(+), 10 deletions(-) create mode 100644 libraries/cmp/cmpEventUtils.ts create mode 100644 test/spec/libraries/cmp/cmpEventUtils_spec.js diff --git a/libraries/cmp/cmpEventUtils.ts b/libraries/cmp/cmpEventUtils.ts new file mode 100644 index 00000000000..4619e9605c9 --- /dev/null +++ b/libraries/cmp/cmpEventUtils.ts @@ -0,0 +1,121 @@ +/** + * Shared utilities for CMP event listener management + * Used by TCF and GPP consent management modules + */ + +import { logError, logInfo } from "../../src/utils.js"; + +export interface CmpEventManager { + cmpApi: any; + listenerId: number | undefined; + setCmpApi(cmpApi: any): void; + getCmpApi(): any; + setCmpListenerId(listenerId: number | undefined): void; + getCmpListenerId(): number | undefined; + removeCmpEventListener(): void; + resetCmpApis(): void; +} + +/** + * Base CMP event manager implementation + */ +export abstract class BaseCmpEventManager implements CmpEventManager { + cmpApi: any = null; + listenerId: number | undefined = undefined; + + setCmpApi(cmpApi: any): void { + this.cmpApi = cmpApi; + } + + getCmpApi(): any { + return this.cmpApi; + } + + setCmpListenerId(listenerId: number | undefined): void { + this.listenerId = listenerId; + } + + getCmpListenerId(): number | undefined { + return this.listenerId; + } + + resetCmpApis(): void { + this.cmpApi = null; + this.listenerId = undefined; + } + + /** + * Helper method to get base removal parameters + * Can be used by subclasses that need to remove event listeners + */ + protected getRemoveListenerParams(): Record | null { + const cmpApi = this.getCmpApi(); + const listenerId = this.getCmpListenerId(); + + // Comprehensive validation for all possible failure scenarios + if (cmpApi && typeof cmpApi === 'function' && listenerId !== undefined && listenerId !== null) { + return { + command: "removeEventListener", + callback: () => this.resetCmpApis(), + parameter: listenerId + }; + } + return null; + } + + /** + * Abstract method - each subclass implements its own removal logic + */ + abstract removeCmpEventListener(): void; +} + +/** + * TCF-specific CMP event manager + */ +export class TcfCmpEventManager extends BaseCmpEventManager { + private getConsentData: () => any; + + constructor(getConsentData?: () => any) { + super(); + this.getConsentData = getConsentData || (() => null); + } + + removeCmpEventListener(): void { + const params = this.getRemoveListenerParams(); + if (params) { + const consentData = this.getConsentData(); + params.apiVersion = consentData?.apiVersion || 2; + logInfo('Removing TCF CMP event listener'); + this.getCmpApi()(params); + } + } +} + +/** + * GPP-specific CMP event manager + * GPP doesn't require event listener removal, so this is empty + */ +export class GppCmpEventManager extends BaseCmpEventManager { + removeCmpEventListener(): void { + const params = this.getRemoveListenerParams(); + if (params) { + logInfo('Removing GPP CMP event listener'); + this.getCmpApi()(params); + } + } +} + +/** + * Factory function to create appropriate CMP event manager + */ +export function createCmpEventManager(type: 'tcf' | 'gpp', getConsentData?: () => any): CmpEventManager { + switch (type) { + case 'tcf': + return new TcfCmpEventManager(getConsentData); + case 'gpp': + return new GppCmpEventManager(); + default: + logError(`Unknown CMP type: ${type}`); + return null; + } +} diff --git a/libraries/consentManagement/cmUtils.ts b/libraries/consentManagement/cmUtils.ts index 88dfffef9cd..d8012241fc7 100644 --- a/libraries/consentManagement/cmUtils.ts +++ b/libraries/consentManagement/cmUtils.ts @@ -112,6 +112,12 @@ export interface BaseCMConfig { * for the user to interact with the CMP. */ actionTimeout?: number; + /** + * Flag to enable or disable the consent management module. + * When set to false, the module will be reset and disabled. + * Defaults to true when not specified. + */ + enabled?: boolean; } export interface IABCMConfig { @@ -136,6 +142,7 @@ export function configParser( parseConsentData, getNullConsent, cmpHandlers, + cmpEventCleanup, DEFAULT_CMP = 'iab', DEFAULT_CONSENT_TIMEOUT = 10000 } = {} as any @@ -167,6 +174,19 @@ export function configParser( getHook('requestBids').getHooks({hook: requestBidsHook}).remove(); buildActivityParams.getHooks({hook: attachActivityParams}).remove(); requestBidsHook = null; + logInfo(`${displayName} consentManagement module has been deactivated...`); + } + } + + function resetConsentDataHandler() { + reset(); + // Call module-specific CMP event cleanup if provided + if (typeof cmpEventCleanup === 'function') { + try { + cmpEventCleanup(); + } catch (e) { + logError(`Error during CMP event cleanup for ${displayName}:`, e); + } } } @@ -177,6 +197,14 @@ export function configParser( reset(); return {}; } + + // Check if module is explicitly disabled + if (cmConfig?.enabled === false) { + logWarn(msg(`config enabled is set to false, disabling consent manager module`)); + resetConsentDataHandler(); + return {}; + } + let cmpHandler; if (isStr(cmConfig.cmpApi)) { cmpHandler = cmConfig.cmpApi; diff --git a/modules/consentManagementGpp.ts b/modules/consentManagementGpp.ts index 905cffda213..fcbcaeb664c 100644 --- a/modules/consentManagementGpp.ts +++ b/modules/consentManagementGpp.ts @@ -11,10 +11,14 @@ import {enrichFPD} from '../src/fpd/enrichment.js'; import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; import {PbPromise, defer} from '../src/utils/promise.js'; import {type CMConfig, configParser} from '../libraries/consentManagement/cmUtils.js'; +import {createCmpEventManager, type CmpEventManager} from '../libraries/cmp/cmpEventUtils.js'; import {CONSENT_GPP} from "../src/consentHandler.ts"; export let consentConfig = {} as any; +// CMP event manager instance for GPP +let gppCmpEventManager: CmpEventManager | null = null; + type RelevantCMPData = { applicableSections: number[] gppString: string; @@ -101,6 +105,13 @@ export class GPPClient { logWarn(`Unrecognized GPP CMP version: ${pingData.apiVersion}. Continuing using GPP API version ${this.apiVersion}...`); } this.initialized = true; + + // Initialize CMP event manager and set CMP API + if (!gppCmpEventManager) { + gppCmpEventManager = createCmpEventManager('gpp'); + } + gppCmpEventManager.setCmpApi(this.cmp); + this.cmp({ command: 'addEventListener', callback: (event, success) => { @@ -120,6 +131,10 @@ export class GPPClient { if (gppDataHandler.getConsentData() != null && event?.pingData != null && !this.isCMPReady(event.pingData)) { gppDataHandler.setConsentData(null); } + + if (event?.listenerId !== null && event?.listenerId !== undefined) { + gppCmpEventManager?.setCmpListenerId(event?.listenerId); + } } }); } @@ -218,13 +233,23 @@ export function resetConsentData() { GPPClient.INST = null; } +export function removeCmpListener() { + // Clean up CMP event listeners before resetting + if (gppCmpEventManager) { + gppCmpEventManager.removeCmpEventListener(); + gppCmpEventManager = null; + } + resetConsentData(); +} + const parseConfig = configParser({ namespace: 'gpp', displayName: 'GPP', consentDataHandler: gppDataHandler, parseConsentData, getNullConsent: () => toConsentData(null), - cmpHandlers: cmpCallMap + cmpHandlers: cmpCallMap, + cmpEventCleanup: removeCmpListener }); export function setConsentConfig(config) { diff --git a/modules/consentManagementTcf.ts b/modules/consentManagementTcf.ts index e693132c8af..673a2d6f269 100644 --- a/modules/consentManagementTcf.ts +++ b/modules/consentManagementTcf.ts @@ -11,6 +11,7 @@ import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {cmpClient} from '../libraries/cmp/cmpClient.js'; import {configParser} from '../libraries/consentManagement/cmUtils.js'; +import {createCmpEventManager, type CmpEventManager} from '../libraries/cmp/cmpEventUtils.js'; import {CONSENT_GDPR} from "../src/consentHandler.ts"; import type {CMConfig} from "../libraries/consentManagement/cmUtils.ts"; @@ -24,6 +25,9 @@ const cmpCallMap = { 'iab': lookupIabConsent, }; +// CMP event manager instance for TCF +export let tcfCmpEventManager: CmpEventManager | null = null; + /** * @see https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework * @see https://github.com/InteractiveAdvertisingBureau/iabtcf-es/tree/master/modules/core#iabtcfcore @@ -87,6 +91,9 @@ function lookupIabConsent(setProvisionalConsent) { if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { try { + if (tcfData.listenerId !== null && tcfData.listenerId !== undefined) { + tcfCmpEventManager?.setCmpListenerId(tcfData.listenerId); + } gdprDataHandler.setConsentData(parseConsentData(tcfData)); resolve(); } catch (e) { @@ -113,6 +120,12 @@ function lookupIabConsent(setProvisionalConsent) { logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); } + // Initialize CMP event manager and set CMP API + if (!tcfCmpEventManager) { + tcfCmpEventManager = createCmpEventManager('tcf', () => gdprDataHandler.getConsentData()); + } + tcfCmpEventManager.setCmpApi(cmp); + cmp({ command: 'addEventListener', callback: cmpResponseCallback @@ -159,14 +172,25 @@ export function resetConsentData() { gdprDataHandler.reset(); } +export function removeCmpListener() { + // Clean up CMP event listeners before resetting + if (tcfCmpEventManager) { + tcfCmpEventManager.removeCmpEventListener(); + tcfCmpEventManager = null; + } + resetConsentData(); +} + const parseConfig = configParser({ namespace: 'gdpr', displayName: 'TCF', consentDataHandler: gdprDataHandler, cmpHandlers: cmpCallMap, parseConsentData, - getNullConsent: () => toConsentData(null) + getNullConsent: () => toConsentData(null), + cmpEventCleanup: removeCmpListener } as any) + /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function */ diff --git a/src/consentHandler.ts b/src/consentHandler.ts index b69f9df0a1a..2691429e241 100644 --- a/src/consentHandler.ts +++ b/src/consentHandler.ts @@ -108,12 +108,19 @@ export class ConsentHandler { } getConsentData(): T { - return this.#data; + if (this.#enabled) { + return this.#data; + } + return null; } get hash() { if (this.#dirty) { - this.#hash = cyrb53Hash(JSON.stringify(this.#data && this.hashFields ? this.hashFields.map(f => this.#data[f]) : this.#data)) + this.#hash = cyrb53Hash( + JSON.stringify( + this.#data && this.hashFields ? this.hashFields.map((f) => this.#data[f]) : this.#data + ) + ); this.#dirty = false; } return this.#hash; @@ -132,16 +139,21 @@ class UspConsentHandler extends ConsentHandler> { - hashFields = ['gdprApplies', 'consentString'] + hashFields = ["gdprApplies", "consentString"]; + /** + * Remove CMP event listener using CMP API + */ getConsentMeta() { const consentData = this.getConsentData(); if (consentData && consentData.vendorData && this.generatedTime) { return { gdprApplies: consentData.gdprApplies as boolean, - consentStringSize: (isStr(consentData.vendorData.tcString)) ? consentData.vendorData.tcString.length : 0, + consentStringSize: isStr(consentData.vendorData.tcString) + ? consentData.vendorData.tcString.length + : 0, generatedAt: this.generatedTime, - apiVersion: consentData.apiVersion - } + apiVersion: consentData.apiVersion, + }; } } } diff --git a/test/spec/libraries/cmUtils_spec.js b/test/spec/libraries/cmUtils_spec.js index 7f9c2c932b3..be2e31a8e18 100644 --- a/test/spec/libraries/cmUtils_spec.js +++ b/test/spec/libraries/cmUtils_spec.js @@ -1,5 +1,5 @@ import * as utils from 'src/utils.js'; -import {lookupConsentData, consentManagementHook} from '../../../libraries/consentManagement/cmUtils.js'; +import {lookupConsentData, consentManagementHook, configParser} from '../../../libraries/consentManagement/cmUtils.js'; describe('consent management utils', () => { let sandbox, clock; @@ -226,4 +226,126 @@ describe('consent management utils', () => { }) }); }); + + describe('configParser', () => { + let namespace, displayName, consentDataHandler, parseConsentData, getNullConsent, cmpHandlers, cmpEventCleanup; + let getConsentConfig, resetConsentDataHandler; + + beforeEach(() => { + namespace = 'test'; + displayName = 'TEST'; + resetConsentDataHandler = sinon.stub(); + consentDataHandler = { + reset: sinon.stub(), + removeCmpEventListener: sinon.stub(), + getConsentData: sinon.stub(), + setConsentData: sinon.stub() + }; + parseConsentData = sinon.stub().callsFake(data => data); + getNullConsent = sinon.stub().returns({consent: null}); + cmpHandlers = { + iab: sinon.stub().returns(Promise.resolve()) + }; + cmpEventCleanup = sinon.stub(); + + // Create a spy for resetConsentDataHandler to verify it's called + const configParserInstance = configParser({ + namespace, + displayName, + consentDataHandler, + parseConsentData, + getNullConsent, + cmpHandlers, + cmpEventCleanup + }); + + getConsentConfig = configParserInstance; + }); + + it('should reset and return empty object when config is not defined', () => { + const result = getConsentConfig(); + expect(result).to.deep.equal({}); + sinon.assert.calledWith(utils.logWarn, sinon.match('config not defined')); + }); + + it('should reset and return empty object when config is not an object', () => { + const result = getConsentConfig({[namespace]: 'not an object'}); + expect(result).to.deep.equal({}); + sinon.assert.calledWith(utils.logWarn, sinon.match('config not defined')); + }); + + describe('when module is explicitly disabled', () => { + it('should reset consent data handler and return empty object when enabled is false', () => { + const result = getConsentConfig({[namespace]: {enabled: false}}); + + expect(result).to.deep.equal({}); + sinon.assert.calledWith(utils.logWarn, sinon.match('config enabled is set to false')); + }); + + it('should call cmpEventCleanup when enabled is false', () => { + getConsentConfig({[namespace]: {enabled: false}}); + + sinon.assert.called(cmpEventCleanup); + sinon.assert.calledWith(utils.logWarn, sinon.match('config enabled is set to false')); + }); + + it('should handle cmpEventCleanup errors gracefully', () => { + const cleanupError = new Error('Cleanup failed'); + cmpEventCleanup.throws(cleanupError); + + getConsentConfig({[namespace]: {enabled: false}}); + + sinon.assert.called(cmpEventCleanup); + sinon.assert.calledWith(utils.logError, sinon.match('Error during CMP event cleanup'), cleanupError); + }); + + it('should not call cmpEventCleanup when enabled is true', () => { + getConsentConfig({[namespace]: {enabled: true, cmpApi: 'iab'}}); + + sinon.assert.notCalled(cmpEventCleanup); + }); + + it('should not call cmpEventCleanup when enabled is not specified', () => { + getConsentConfig({[namespace]: {cmpApi: 'iab'}}); + + sinon.assert.notCalled(cmpEventCleanup); + }); + }); + + describe('cmpEventCleanup parameter', () => { + it('should work without cmpEventCleanup parameter', () => { + const configParserWithoutCleanup = configParser({ + namespace, + displayName, + consentDataHandler, + parseConsentData, + getNullConsent, + cmpHandlers + // No cmpEventCleanup provided + }); + + const result = configParserWithoutCleanup({[namespace]: {enabled: false}}); + expect(result).to.deep.equal({}); + // Should not throw error when cmpEventCleanup is undefined + }); + + it('should only call cmpEventCleanup if it is a function', () => { + const configParserWithNonFunction = configParser({ + namespace, + displayName, + consentDataHandler, + parseConsentData, + getNullConsent, + cmpHandlers, + cmpEventCleanup: 'not a function' + }); + + const result = configParserWithNonFunction({[namespace]: {enabled: false}}); + expect(result).to.deep.equal({}); + // Should not throw error when cmpEventCleanup is not a function + }); + }); + + // Additional tests for other configParser functionality could be added here + }); }); diff --git a/test/spec/libraries/cmp/cmpEventUtils_spec.js b/test/spec/libraries/cmp/cmpEventUtils_spec.js new file mode 100644 index 00000000000..c3262549b20 --- /dev/null +++ b/test/spec/libraries/cmp/cmpEventUtils_spec.js @@ -0,0 +1,330 @@ +import * as utils from 'src/utils.js'; +import { + BaseCmpEventManager, + TcfCmpEventManager, + GppCmpEventManager, + createCmpEventManager +} from '../../../../libraries/cmp/cmpEventUtils.js'; + +describe('CMP Event Utils', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + ['logError', 'logInfo', 'logWarn'].forEach(n => sandbox.stub(utils, n)); + }); + afterEach(() => { + sandbox.restore(); + }); + + describe('BaseCmpEventManager', () => { + let manager; + + // Create a concrete implementation for testing the abstract class + class TestCmpEventManager extends BaseCmpEventManager { + removeCmpEventListener() { + const params = this.getRemoveListenerParams(); + if (params) { + this.getCmpApi()(params); + } + } + } + + beforeEach(() => { + manager = new TestCmpEventManager(); + }); + + describe('setCmpApi and getCmpApi', () => { + it('should set and get CMP API', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + expect(manager.getCmpApi()).to.equal(mockCmpApi); + }); + + it('should initialize with null CMP API', () => { + expect(manager.getCmpApi()).to.be.null; + }); + }); + + describe('setCmpListenerId and getCmpListenerId', () => { + it('should set and get listener ID', () => { + manager.setCmpListenerId(123); + expect(manager.getCmpListenerId()).to.equal(123); + }); + + it('should handle undefined listener ID', () => { + manager.setCmpListenerId(undefined); + expect(manager.getCmpListenerId()).to.be.undefined; + }); + + it('should handle zero as valid listener ID', () => { + manager.setCmpListenerId(0); + expect(manager.getCmpListenerId()).to.equal(0); + }); + + it('should initialize with undefined listener ID', () => { + expect(manager.getCmpListenerId()).to.be.undefined; + }); + }); + + describe('resetCmpApis', () => { + it('should reset both CMP API and listener ID', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(456); + + manager.resetCmpApis(); + + expect(manager.getCmpApi()).to.be.null; + expect(manager.getCmpListenerId()).to.be.undefined; + }); + }); + + describe('getRemoveListenerParams', () => { + it('should return params when CMP API and listener ID are valid', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(123); + + const params = manager.getRemoveListenerParams(); + + expect(params).to.not.be.null; + expect(params.command).to.equal('removeEventListener'); + expect(params.parameter).to.equal(123); + expect(params.callback).to.be.a('function'); + }); + + it('should return null when CMP API is null', () => { + manager.setCmpApi(null); + manager.setCmpListenerId(123); + + const params = manager.getRemoveListenerParams(); + expect(params).to.be.null; + }); + + it('should return null when CMP API is not a function', () => { + manager.setCmpApi('not a function'); + manager.setCmpListenerId(123); + + const params = manager.getRemoveListenerParams(); + expect(params).to.be.null; + }); + + it('should return null when listener ID is undefined', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(undefined); + + const params = manager.getRemoveListenerParams(); + expect(params).to.be.null; + }); + + it('should return null when listener ID is null', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(null); + + const params = manager.getRemoveListenerParams(); + expect(params).to.be.null; + }); + + it('should return params when listener ID is 0', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(0); + + const params = manager.getRemoveListenerParams(); + expect(params).to.not.be.null; + expect(params.parameter).to.equal(0); + }); + + it('should call resetCmpApis when callback is executed', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(123); + + const params = manager.getRemoveListenerParams(); + params.callback(); + + expect(manager.getCmpApi()).to.be.null; + expect(manager.getCmpListenerId()).to.be.undefined; + }); + }); + + describe('removeCmpEventListener', () => { + it('should call CMP API with params when conditions are met', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(123); + + manager.removeCmpEventListener(); + + sinon.assert.calledOnce(mockCmpApi); + const callArgs = mockCmpApi.getCall(0).args[0]; + expect(callArgs.command).to.equal('removeEventListener'); + expect(callArgs.parameter).to.equal(123); + }); + + it('should not call CMP API when conditions are not met', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + // No listener ID set + + manager.removeCmpEventListener(); + + sinon.assert.notCalled(mockCmpApi); + }); + }); + }); + + describe('TcfCmpEventManager', () => { + let manager, mockGetConsentData; + + beforeEach(() => { + mockGetConsentData = sinon.stub(); + manager = new TcfCmpEventManager(mockGetConsentData); + }); + + it('should initialize with provided getConsentData function', () => { + expect(manager.getConsentData).to.equal(mockGetConsentData); + }); + + it('should initialize with default getConsentData when not provided', () => { + const defaultManager = new TcfCmpEventManager(); + expect(defaultManager.getConsentData()).to.be.null; + }); + + describe('removeCmpEventListener', () => { + it('should call CMP API with TCF-specific params including apiVersion', () => { + const mockCmpApi = sinon.stub(); + const consentData = { apiVersion: 2 }; + mockGetConsentData.returns(consentData); + + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(456); + + manager.removeCmpEventListener(); + + sinon.assert.calledOnce(mockCmpApi); + sinon.assert.calledWith(utils.logInfo, 'Removing TCF CMP event listener'); + + const callArgs = mockCmpApi.getCall(0).args[0]; + expect(callArgs.command).to.equal('removeEventListener'); + expect(callArgs.parameter).to.equal(456); + expect(callArgs.apiVersion).to.equal(2); + }); + + it('should use default apiVersion when consent data has no apiVersion', () => { + const mockCmpApi = sinon.stub(); + const consentData = {}; // No apiVersion + mockGetConsentData.returns(consentData); + + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(789); + + manager.removeCmpEventListener(); + + const callArgs = mockCmpApi.getCall(0).args[0]; + expect(callArgs.apiVersion).to.equal(2); + }); + + it('should use default apiVersion when consent data is null', () => { + const mockCmpApi = sinon.stub(); + mockGetConsentData.returns(null); + + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(789); + + manager.removeCmpEventListener(); + + const callArgs = mockCmpApi.getCall(0).args[0]; + expect(callArgs.apiVersion).to.equal(2); + }); + + it('should not call CMP API when conditions are not met', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + // No listener ID set + + manager.removeCmpEventListener(); + + sinon.assert.notCalled(mockCmpApi); + sinon.assert.notCalled(utils.logInfo); + }); + }); + }); + + describe('GppCmpEventManager', () => { + let manager; + + beforeEach(() => { + manager = new GppCmpEventManager(); + }); + + describe('removeCmpEventListener', () => { + it('should call CMP API with GPP-specific params', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + manager.setCmpListenerId(321); + + manager.removeCmpEventListener(); + + sinon.assert.calledOnce(mockCmpApi); + sinon.assert.calledWith(utils.logInfo, 'Removing GPP CMP event listener'); + + const callArgs = mockCmpApi.getCall(0).args[0]; + expect(callArgs.command).to.equal('removeEventListener'); + expect(callArgs.parameter).to.equal(321); + expect(callArgs.apiVersion).to.be.undefined; // GPP doesn't set apiVersion + }); + + it('should not call CMP API when conditions are not met', () => { + const mockCmpApi = sinon.stub(); + manager.setCmpApi(mockCmpApi); + // No listener ID set + + manager.removeCmpEventListener(); + + sinon.assert.notCalled(mockCmpApi); + sinon.assert.notCalled(utils.logInfo); + }); + }); + }); + + describe('createCmpEventManager', () => { + it('should create TcfCmpEventManager for tcf type', () => { + const mockGetConsentData = sinon.stub(); + const manager = createCmpEventManager('tcf', mockGetConsentData); + + expect(manager).to.be.instanceOf(TcfCmpEventManager); + expect(manager.getConsentData).to.equal(mockGetConsentData); + }); + + it('should create TcfCmpEventManager without getConsentData function', () => { + const manager = createCmpEventManager('tcf'); + + expect(manager).to.be.instanceOf(TcfCmpEventManager); + expect(manager.getConsentData()).to.be.null; + }); + + it('should create GppCmpEventManager for gpp type', () => { + const manager = createCmpEventManager('gpp'); + + expect(manager).to.be.instanceOf(GppCmpEventManager); + }); + + it('should log error and return null for unknown type', () => { + const manager = createCmpEventManager('unknown'); + + expect(manager).to.be.null; + sinon.assert.calledWith(utils.logError, 'Unknown CMP type: unknown'); + }); + + it('should ignore getConsentData parameter for gpp type', () => { + const mockGetConsentData = sinon.stub(); + const manager = createCmpEventManager('gpp', mockGetConsentData); + + expect(manager).to.be.instanceOf(GppCmpEventManager); + // GPP manager doesn't use getConsentData + }); + }); +}); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index a033c56ddcd..dfe5f3d6a0e 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,4 @@ -import {consentConfig, gdprScope, resetConsentData, setConsentConfig, } from 'modules/consentManagementTcf.js'; +import {consentConfig, gdprScope, resetConsentData, setConsentConfig, tcfCmpEventManager} from 'modules/consentManagementTcf.js'; import {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; @@ -736,6 +736,125 @@ describe('consentManagement', function () { expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); }); + + it('should set CMP listener ID when listenerId is provided in tcfData', async function () { + const testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + listenerId: 123 + }; + + // Create a spy that will be applied when tcfCmpEventManager is created + let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); + + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; + + sinon.assert.calledOnce(setCmpListenerIdSpy); + sinon.assert.calledWith(setCmpListenerIdSpy, 123); + + setCmpListenerIdSpy.restore(); + }); + + it('should not set CMP listener ID when listenerId is null', async function () { + const testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + listenerId: null + }; + + // Create a spy that will be applied when tcfCmpEventManager is created + let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); + + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; + + sinon.assert.notCalled(setCmpListenerIdSpy); + + setCmpListenerIdSpy.restore(); + }); + + it('should not set CMP listener ID when listenerId is undefined', async function () { + const testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + listenerId: undefined + }; + + // Create a spy that will be applied when tcfCmpEventManager is created + let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); + + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; + + sinon.assert.notCalled(setCmpListenerIdSpy); + setCmpListenerIdSpy.restore(); + }); + + it('should set CMP listener ID when listenerId is 0 (valid listener ID)', async function () { + const testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + listenerId: 0 + }; + + // Create a spy that will be applied when tcfCmpEventManager is created + let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); + + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; + + sinon.assert.calledOnce(setCmpListenerIdSpy); + sinon.assert.calledWith(setCmpListenerIdSpy, 0); + setCmpListenerIdSpy.restore(); + }); + + it('should set CMP API reference when CMP is found', async function () { + const testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }; + + // Create a spy that will be applied when tcfCmpEventManager is created + let setCmpApiSpy = sinon.spy(tcfCmpEventManager, 'setCmpApi'); + + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; + + sinon.assert.calledOnce(setCmpApiSpy); + expect(setCmpApiSpy.getCall(0).args[0]).to.be.a('function'); + setCmpApiSpy.restore(); + }); }); }); }); From 54f2515d445fbfbe601db593601800244dfc69c6 Mon Sep 17 00:00:00 2001 From: rororo <16588724+rororo@users.noreply.github.com> Date: Fri, 24 Oct 2025 00:18:15 +0900 Subject: [PATCH 055/147] suim Bidder: Change api endpoint (#14060) * change api endpoint * Fix test --- modules/suimBidAdapter.js | 5 ++--- test/spec/modules/suimBidAdapter_spec.js | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/suimBidAdapter.js b/modules/suimBidAdapter.js index 0e4374a83f5..57ecbcaac1e 100644 --- a/modules/suimBidAdapter.js +++ b/modules/suimBidAdapter.js @@ -14,8 +14,8 @@ import { getBidIdParameter, isEmpty } from '../src/utils.js'; */ const BIDDER_CODE = 'suim'; -const ENDPOINT = 'https://bid.suimad.com/api/v1/prebids'; -const SYNC_URL = 'https://bid.suimad.com/api/v1/logs/usersync'; +const ENDPOINT = 'https://ad.suimad.com/bid'; +const SYNC_URL = 'https://ad.suimad.com/usersync'; export const spec = { code: BIDDER_CODE, @@ -60,7 +60,6 @@ export const spec = { data: data, options: { contentType: 'text/plain', - withCredentials: false, }, }; }); diff --git a/test/spec/modules/suimBidAdapter_spec.js b/test/spec/modules/suimBidAdapter_spec.js index e06e5875d7c..ca537ba131b 100644 --- a/test/spec/modules/suimBidAdapter_spec.js +++ b/test/spec/modules/suimBidAdapter_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; import { spec } from 'modules/suimBidAdapter.js'; -const ENDPOINT = 'https://bid.suimad.com/api/v1/prebids'; -const SYNC_URL = 'https://bid.suimad.com/api/v1/logs/usersync'; +const ENDPOINT = 'https://ad.suimad.com/bid'; +const SYNC_URL = 'https://ad.suimad.com/usersync'; describe('SuimAdapter', function () { describe('isBidRequestValid', function () { @@ -81,7 +81,7 @@ describe('SuimAdapter', function () { describe('interpretResponse', function () { const bidResponse = { - bidId: '22a91eced2e93a', + requestId: '22a91eced2e93a', cpm: 300, currency: 'JPY', width: 300, @@ -115,7 +115,7 @@ describe('SuimAdapter', function () { const result = spec.interpretResponse({ body: bidResponse }, bidderRequests); expect(result).to.have.lengthOf(1); expect(result[0]).to.deep.equal({ - requestId: bidResponse.bid, + requestId: bidResponse.requestId, cpm: 300, currency: 'JPY', width: 300, From dedba3912e7befd5763def9013cbc17183de0f64 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 23 Oct 2025 11:22:30 -0400 Subject: [PATCH 056/147] =?UTF-8?q?Revert=20"Consent=20Management=20:=20re?= =?UTF-8?q?set=20functionality=20to=20properly=20disable=20TCF/GPP=20?= =?UTF-8?q?=E2=80=A6"=20(#14063)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 65e6c0bc5bad095d51cbb75637badebb2eabb369. --- libraries/cmp/cmpEventUtils.ts | 121 ------- libraries/consentManagement/cmUtils.ts | 28 -- modules/consentManagementGpp.ts | 27 +- modules/consentManagementTcf.ts | 26 +- src/consentHandler.ts | 24 +- test/spec/libraries/cmUtils_spec.js | 124 +------ test/spec/libraries/cmp/cmpEventUtils_spec.js | 330 ------------------ test/spec/modules/consentManagement_spec.js | 121 +------ 8 files changed, 10 insertions(+), 791 deletions(-) delete mode 100644 libraries/cmp/cmpEventUtils.ts delete mode 100644 test/spec/libraries/cmp/cmpEventUtils_spec.js diff --git a/libraries/cmp/cmpEventUtils.ts b/libraries/cmp/cmpEventUtils.ts deleted file mode 100644 index 4619e9605c9..00000000000 --- a/libraries/cmp/cmpEventUtils.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Shared utilities for CMP event listener management - * Used by TCF and GPP consent management modules - */ - -import { logError, logInfo } from "../../src/utils.js"; - -export interface CmpEventManager { - cmpApi: any; - listenerId: number | undefined; - setCmpApi(cmpApi: any): void; - getCmpApi(): any; - setCmpListenerId(listenerId: number | undefined): void; - getCmpListenerId(): number | undefined; - removeCmpEventListener(): void; - resetCmpApis(): void; -} - -/** - * Base CMP event manager implementation - */ -export abstract class BaseCmpEventManager implements CmpEventManager { - cmpApi: any = null; - listenerId: number | undefined = undefined; - - setCmpApi(cmpApi: any): void { - this.cmpApi = cmpApi; - } - - getCmpApi(): any { - return this.cmpApi; - } - - setCmpListenerId(listenerId: number | undefined): void { - this.listenerId = listenerId; - } - - getCmpListenerId(): number | undefined { - return this.listenerId; - } - - resetCmpApis(): void { - this.cmpApi = null; - this.listenerId = undefined; - } - - /** - * Helper method to get base removal parameters - * Can be used by subclasses that need to remove event listeners - */ - protected getRemoveListenerParams(): Record | null { - const cmpApi = this.getCmpApi(); - const listenerId = this.getCmpListenerId(); - - // Comprehensive validation for all possible failure scenarios - if (cmpApi && typeof cmpApi === 'function' && listenerId !== undefined && listenerId !== null) { - return { - command: "removeEventListener", - callback: () => this.resetCmpApis(), - parameter: listenerId - }; - } - return null; - } - - /** - * Abstract method - each subclass implements its own removal logic - */ - abstract removeCmpEventListener(): void; -} - -/** - * TCF-specific CMP event manager - */ -export class TcfCmpEventManager extends BaseCmpEventManager { - private getConsentData: () => any; - - constructor(getConsentData?: () => any) { - super(); - this.getConsentData = getConsentData || (() => null); - } - - removeCmpEventListener(): void { - const params = this.getRemoveListenerParams(); - if (params) { - const consentData = this.getConsentData(); - params.apiVersion = consentData?.apiVersion || 2; - logInfo('Removing TCF CMP event listener'); - this.getCmpApi()(params); - } - } -} - -/** - * GPP-specific CMP event manager - * GPP doesn't require event listener removal, so this is empty - */ -export class GppCmpEventManager extends BaseCmpEventManager { - removeCmpEventListener(): void { - const params = this.getRemoveListenerParams(); - if (params) { - logInfo('Removing GPP CMP event listener'); - this.getCmpApi()(params); - } - } -} - -/** - * Factory function to create appropriate CMP event manager - */ -export function createCmpEventManager(type: 'tcf' | 'gpp', getConsentData?: () => any): CmpEventManager { - switch (type) { - case 'tcf': - return new TcfCmpEventManager(getConsentData); - case 'gpp': - return new GppCmpEventManager(); - default: - logError(`Unknown CMP type: ${type}`); - return null; - } -} diff --git a/libraries/consentManagement/cmUtils.ts b/libraries/consentManagement/cmUtils.ts index d8012241fc7..88dfffef9cd 100644 --- a/libraries/consentManagement/cmUtils.ts +++ b/libraries/consentManagement/cmUtils.ts @@ -112,12 +112,6 @@ export interface BaseCMConfig { * for the user to interact with the CMP. */ actionTimeout?: number; - /** - * Flag to enable or disable the consent management module. - * When set to false, the module will be reset and disabled. - * Defaults to true when not specified. - */ - enabled?: boolean; } export interface IABCMConfig { @@ -142,7 +136,6 @@ export function configParser( parseConsentData, getNullConsent, cmpHandlers, - cmpEventCleanup, DEFAULT_CMP = 'iab', DEFAULT_CONSENT_TIMEOUT = 10000 } = {} as any @@ -174,19 +167,6 @@ export function configParser( getHook('requestBids').getHooks({hook: requestBidsHook}).remove(); buildActivityParams.getHooks({hook: attachActivityParams}).remove(); requestBidsHook = null; - logInfo(`${displayName} consentManagement module has been deactivated...`); - } - } - - function resetConsentDataHandler() { - reset(); - // Call module-specific CMP event cleanup if provided - if (typeof cmpEventCleanup === 'function') { - try { - cmpEventCleanup(); - } catch (e) { - logError(`Error during CMP event cleanup for ${displayName}:`, e); - } } } @@ -197,14 +177,6 @@ export function configParser( reset(); return {}; } - - // Check if module is explicitly disabled - if (cmConfig?.enabled === false) { - logWarn(msg(`config enabled is set to false, disabling consent manager module`)); - resetConsentDataHandler(); - return {}; - } - let cmpHandler; if (isStr(cmConfig.cmpApi)) { cmpHandler = cmConfig.cmpApi; diff --git a/modules/consentManagementGpp.ts b/modules/consentManagementGpp.ts index fcbcaeb664c..905cffda213 100644 --- a/modules/consentManagementGpp.ts +++ b/modules/consentManagementGpp.ts @@ -11,14 +11,10 @@ import {enrichFPD} from '../src/fpd/enrichment.js'; import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; import {PbPromise, defer} from '../src/utils/promise.js'; import {type CMConfig, configParser} from '../libraries/consentManagement/cmUtils.js'; -import {createCmpEventManager, type CmpEventManager} from '../libraries/cmp/cmpEventUtils.js'; import {CONSENT_GPP} from "../src/consentHandler.ts"; export let consentConfig = {} as any; -// CMP event manager instance for GPP -let gppCmpEventManager: CmpEventManager | null = null; - type RelevantCMPData = { applicableSections: number[] gppString: string; @@ -105,13 +101,6 @@ export class GPPClient { logWarn(`Unrecognized GPP CMP version: ${pingData.apiVersion}. Continuing using GPP API version ${this.apiVersion}...`); } this.initialized = true; - - // Initialize CMP event manager and set CMP API - if (!gppCmpEventManager) { - gppCmpEventManager = createCmpEventManager('gpp'); - } - gppCmpEventManager.setCmpApi(this.cmp); - this.cmp({ command: 'addEventListener', callback: (event, success) => { @@ -131,10 +120,6 @@ export class GPPClient { if (gppDataHandler.getConsentData() != null && event?.pingData != null && !this.isCMPReady(event.pingData)) { gppDataHandler.setConsentData(null); } - - if (event?.listenerId !== null && event?.listenerId !== undefined) { - gppCmpEventManager?.setCmpListenerId(event?.listenerId); - } } }); } @@ -233,23 +218,13 @@ export function resetConsentData() { GPPClient.INST = null; } -export function removeCmpListener() { - // Clean up CMP event listeners before resetting - if (gppCmpEventManager) { - gppCmpEventManager.removeCmpEventListener(); - gppCmpEventManager = null; - } - resetConsentData(); -} - const parseConfig = configParser({ namespace: 'gpp', displayName: 'GPP', consentDataHandler: gppDataHandler, parseConsentData, getNullConsent: () => toConsentData(null), - cmpHandlers: cmpCallMap, - cmpEventCleanup: removeCmpListener + cmpHandlers: cmpCallMap }); export function setConsentConfig(config) { diff --git a/modules/consentManagementTcf.ts b/modules/consentManagementTcf.ts index 673a2d6f269..e693132c8af 100644 --- a/modules/consentManagementTcf.ts +++ b/modules/consentManagementTcf.ts @@ -11,7 +11,6 @@ import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {cmpClient} from '../libraries/cmp/cmpClient.js'; import {configParser} from '../libraries/consentManagement/cmUtils.js'; -import {createCmpEventManager, type CmpEventManager} from '../libraries/cmp/cmpEventUtils.js'; import {CONSENT_GDPR} from "../src/consentHandler.ts"; import type {CMConfig} from "../libraries/consentManagement/cmUtils.ts"; @@ -25,9 +24,6 @@ const cmpCallMap = { 'iab': lookupIabConsent, }; -// CMP event manager instance for TCF -export let tcfCmpEventManager: CmpEventManager | null = null; - /** * @see https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework * @see https://github.com/InteractiveAdvertisingBureau/iabtcf-es/tree/master/modules/core#iabtcfcore @@ -91,9 +87,6 @@ function lookupIabConsent(setProvisionalConsent) { if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { try { - if (tcfData.listenerId !== null && tcfData.listenerId !== undefined) { - tcfCmpEventManager?.setCmpListenerId(tcfData.listenerId); - } gdprDataHandler.setConsentData(parseConsentData(tcfData)); resolve(); } catch (e) { @@ -120,12 +113,6 @@ function lookupIabConsent(setProvisionalConsent) { logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); } - // Initialize CMP event manager and set CMP API - if (!tcfCmpEventManager) { - tcfCmpEventManager = createCmpEventManager('tcf', () => gdprDataHandler.getConsentData()); - } - tcfCmpEventManager.setCmpApi(cmp); - cmp({ command: 'addEventListener', callback: cmpResponseCallback @@ -172,25 +159,14 @@ export function resetConsentData() { gdprDataHandler.reset(); } -export function removeCmpListener() { - // Clean up CMP event listeners before resetting - if (tcfCmpEventManager) { - tcfCmpEventManager.removeCmpEventListener(); - tcfCmpEventManager = null; - } - resetConsentData(); -} - const parseConfig = configParser({ namespace: 'gdpr', displayName: 'TCF', consentDataHandler: gdprDataHandler, cmpHandlers: cmpCallMap, parseConsentData, - getNullConsent: () => toConsentData(null), - cmpEventCleanup: removeCmpListener + getNullConsent: () => toConsentData(null) } as any) - /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function */ diff --git a/src/consentHandler.ts b/src/consentHandler.ts index 2691429e241..b69f9df0a1a 100644 --- a/src/consentHandler.ts +++ b/src/consentHandler.ts @@ -108,19 +108,12 @@ export class ConsentHandler { } getConsentData(): T { - if (this.#enabled) { - return this.#data; - } - return null; + return this.#data; } get hash() { if (this.#dirty) { - this.#hash = cyrb53Hash( - JSON.stringify( - this.#data && this.hashFields ? this.hashFields.map((f) => this.#data[f]) : this.#data - ) - ); + this.#hash = cyrb53Hash(JSON.stringify(this.#data && this.hashFields ? this.hashFields.map(f => this.#data[f]) : this.#data)) this.#dirty = false; } return this.#hash; @@ -139,21 +132,16 @@ class UspConsentHandler extends ConsentHandler> { - hashFields = ["gdprApplies", "consentString"]; - /** - * Remove CMP event listener using CMP API - */ + hashFields = ['gdprApplies', 'consentString'] getConsentMeta() { const consentData = this.getConsentData(); if (consentData && consentData.vendorData && this.generatedTime) { return { gdprApplies: consentData.gdprApplies as boolean, - consentStringSize: isStr(consentData.vendorData.tcString) - ? consentData.vendorData.tcString.length - : 0, + consentStringSize: (isStr(consentData.vendorData.tcString)) ? consentData.vendorData.tcString.length : 0, generatedAt: this.generatedTime, - apiVersion: consentData.apiVersion, - }; + apiVersion: consentData.apiVersion + } } } } diff --git a/test/spec/libraries/cmUtils_spec.js b/test/spec/libraries/cmUtils_spec.js index be2e31a8e18..7f9c2c932b3 100644 --- a/test/spec/libraries/cmUtils_spec.js +++ b/test/spec/libraries/cmUtils_spec.js @@ -1,5 +1,5 @@ import * as utils from 'src/utils.js'; -import {lookupConsentData, consentManagementHook, configParser} from '../../../libraries/consentManagement/cmUtils.js'; +import {lookupConsentData, consentManagementHook} from '../../../libraries/consentManagement/cmUtils.js'; describe('consent management utils', () => { let sandbox, clock; @@ -226,126 +226,4 @@ describe('consent management utils', () => { }) }); }); - - describe('configParser', () => { - let namespace, displayName, consentDataHandler, parseConsentData, getNullConsent, cmpHandlers, cmpEventCleanup; - let getConsentConfig, resetConsentDataHandler; - - beforeEach(() => { - namespace = 'test'; - displayName = 'TEST'; - resetConsentDataHandler = sinon.stub(); - consentDataHandler = { - reset: sinon.stub(), - removeCmpEventListener: sinon.stub(), - getConsentData: sinon.stub(), - setConsentData: sinon.stub() - }; - parseConsentData = sinon.stub().callsFake(data => data); - getNullConsent = sinon.stub().returns({consent: null}); - cmpHandlers = { - iab: sinon.stub().returns(Promise.resolve()) - }; - cmpEventCleanup = sinon.stub(); - - // Create a spy for resetConsentDataHandler to verify it's called - const configParserInstance = configParser({ - namespace, - displayName, - consentDataHandler, - parseConsentData, - getNullConsent, - cmpHandlers, - cmpEventCleanup - }); - - getConsentConfig = configParserInstance; - }); - - it('should reset and return empty object when config is not defined', () => { - const result = getConsentConfig(); - expect(result).to.deep.equal({}); - sinon.assert.calledWith(utils.logWarn, sinon.match('config not defined')); - }); - - it('should reset and return empty object when config is not an object', () => { - const result = getConsentConfig({[namespace]: 'not an object'}); - expect(result).to.deep.equal({}); - sinon.assert.calledWith(utils.logWarn, sinon.match('config not defined')); - }); - - describe('when module is explicitly disabled', () => { - it('should reset consent data handler and return empty object when enabled is false', () => { - const result = getConsentConfig({[namespace]: {enabled: false}}); - - expect(result).to.deep.equal({}); - sinon.assert.calledWith(utils.logWarn, sinon.match('config enabled is set to false')); - }); - - it('should call cmpEventCleanup when enabled is false', () => { - getConsentConfig({[namespace]: {enabled: false}}); - - sinon.assert.called(cmpEventCleanup); - sinon.assert.calledWith(utils.logWarn, sinon.match('config enabled is set to false')); - }); - - it('should handle cmpEventCleanup errors gracefully', () => { - const cleanupError = new Error('Cleanup failed'); - cmpEventCleanup.throws(cleanupError); - - getConsentConfig({[namespace]: {enabled: false}}); - - sinon.assert.called(cmpEventCleanup); - sinon.assert.calledWith(utils.logError, sinon.match('Error during CMP event cleanup'), cleanupError); - }); - - it('should not call cmpEventCleanup when enabled is true', () => { - getConsentConfig({[namespace]: {enabled: true, cmpApi: 'iab'}}); - - sinon.assert.notCalled(cmpEventCleanup); - }); - - it('should not call cmpEventCleanup when enabled is not specified', () => { - getConsentConfig({[namespace]: {cmpApi: 'iab'}}); - - sinon.assert.notCalled(cmpEventCleanup); - }); - }); - - describe('cmpEventCleanup parameter', () => { - it('should work without cmpEventCleanup parameter', () => { - const configParserWithoutCleanup = configParser({ - namespace, - displayName, - consentDataHandler, - parseConsentData, - getNullConsent, - cmpHandlers - // No cmpEventCleanup provided - }); - - const result = configParserWithoutCleanup({[namespace]: {enabled: false}}); - expect(result).to.deep.equal({}); - // Should not throw error when cmpEventCleanup is undefined - }); - - it('should only call cmpEventCleanup if it is a function', () => { - const configParserWithNonFunction = configParser({ - namespace, - displayName, - consentDataHandler, - parseConsentData, - getNullConsent, - cmpHandlers, - cmpEventCleanup: 'not a function' - }); - - const result = configParserWithNonFunction({[namespace]: {enabled: false}}); - expect(result).to.deep.equal({}); - // Should not throw error when cmpEventCleanup is not a function - }); - }); - - // Additional tests for other configParser functionality could be added here - }); }); diff --git a/test/spec/libraries/cmp/cmpEventUtils_spec.js b/test/spec/libraries/cmp/cmpEventUtils_spec.js deleted file mode 100644 index c3262549b20..00000000000 --- a/test/spec/libraries/cmp/cmpEventUtils_spec.js +++ /dev/null @@ -1,330 +0,0 @@ -import * as utils from 'src/utils.js'; -import { - BaseCmpEventManager, - TcfCmpEventManager, - GppCmpEventManager, - createCmpEventManager -} from '../../../../libraries/cmp/cmpEventUtils.js'; - -describe('CMP Event Utils', () => { - let sandbox; - beforeEach(() => { - sandbox = sinon.createSandbox(); - ['logError', 'logInfo', 'logWarn'].forEach(n => sandbox.stub(utils, n)); - }); - afterEach(() => { - sandbox.restore(); - }); - - describe('BaseCmpEventManager', () => { - let manager; - - // Create a concrete implementation for testing the abstract class - class TestCmpEventManager extends BaseCmpEventManager { - removeCmpEventListener() { - const params = this.getRemoveListenerParams(); - if (params) { - this.getCmpApi()(params); - } - } - } - - beforeEach(() => { - manager = new TestCmpEventManager(); - }); - - describe('setCmpApi and getCmpApi', () => { - it('should set and get CMP API', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - expect(manager.getCmpApi()).to.equal(mockCmpApi); - }); - - it('should initialize with null CMP API', () => { - expect(manager.getCmpApi()).to.be.null; - }); - }); - - describe('setCmpListenerId and getCmpListenerId', () => { - it('should set and get listener ID', () => { - manager.setCmpListenerId(123); - expect(manager.getCmpListenerId()).to.equal(123); - }); - - it('should handle undefined listener ID', () => { - manager.setCmpListenerId(undefined); - expect(manager.getCmpListenerId()).to.be.undefined; - }); - - it('should handle zero as valid listener ID', () => { - manager.setCmpListenerId(0); - expect(manager.getCmpListenerId()).to.equal(0); - }); - - it('should initialize with undefined listener ID', () => { - expect(manager.getCmpListenerId()).to.be.undefined; - }); - }); - - describe('resetCmpApis', () => { - it('should reset both CMP API and listener ID', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(456); - - manager.resetCmpApis(); - - expect(manager.getCmpApi()).to.be.null; - expect(manager.getCmpListenerId()).to.be.undefined; - }); - }); - - describe('getRemoveListenerParams', () => { - it('should return params when CMP API and listener ID are valid', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(123); - - const params = manager.getRemoveListenerParams(); - - expect(params).to.not.be.null; - expect(params.command).to.equal('removeEventListener'); - expect(params.parameter).to.equal(123); - expect(params.callback).to.be.a('function'); - }); - - it('should return null when CMP API is null', () => { - manager.setCmpApi(null); - manager.setCmpListenerId(123); - - const params = manager.getRemoveListenerParams(); - expect(params).to.be.null; - }); - - it('should return null when CMP API is not a function', () => { - manager.setCmpApi('not a function'); - manager.setCmpListenerId(123); - - const params = manager.getRemoveListenerParams(); - expect(params).to.be.null; - }); - - it('should return null when listener ID is undefined', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(undefined); - - const params = manager.getRemoveListenerParams(); - expect(params).to.be.null; - }); - - it('should return null when listener ID is null', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(null); - - const params = manager.getRemoveListenerParams(); - expect(params).to.be.null; - }); - - it('should return params when listener ID is 0', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(0); - - const params = manager.getRemoveListenerParams(); - expect(params).to.not.be.null; - expect(params.parameter).to.equal(0); - }); - - it('should call resetCmpApis when callback is executed', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(123); - - const params = manager.getRemoveListenerParams(); - params.callback(); - - expect(manager.getCmpApi()).to.be.null; - expect(manager.getCmpListenerId()).to.be.undefined; - }); - }); - - describe('removeCmpEventListener', () => { - it('should call CMP API with params when conditions are met', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(123); - - manager.removeCmpEventListener(); - - sinon.assert.calledOnce(mockCmpApi); - const callArgs = mockCmpApi.getCall(0).args[0]; - expect(callArgs.command).to.equal('removeEventListener'); - expect(callArgs.parameter).to.equal(123); - }); - - it('should not call CMP API when conditions are not met', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - // No listener ID set - - manager.removeCmpEventListener(); - - sinon.assert.notCalled(mockCmpApi); - }); - }); - }); - - describe('TcfCmpEventManager', () => { - let manager, mockGetConsentData; - - beforeEach(() => { - mockGetConsentData = sinon.stub(); - manager = new TcfCmpEventManager(mockGetConsentData); - }); - - it('should initialize with provided getConsentData function', () => { - expect(manager.getConsentData).to.equal(mockGetConsentData); - }); - - it('should initialize with default getConsentData when not provided', () => { - const defaultManager = new TcfCmpEventManager(); - expect(defaultManager.getConsentData()).to.be.null; - }); - - describe('removeCmpEventListener', () => { - it('should call CMP API with TCF-specific params including apiVersion', () => { - const mockCmpApi = sinon.stub(); - const consentData = { apiVersion: 2 }; - mockGetConsentData.returns(consentData); - - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(456); - - manager.removeCmpEventListener(); - - sinon.assert.calledOnce(mockCmpApi); - sinon.assert.calledWith(utils.logInfo, 'Removing TCF CMP event listener'); - - const callArgs = mockCmpApi.getCall(0).args[0]; - expect(callArgs.command).to.equal('removeEventListener'); - expect(callArgs.parameter).to.equal(456); - expect(callArgs.apiVersion).to.equal(2); - }); - - it('should use default apiVersion when consent data has no apiVersion', () => { - const mockCmpApi = sinon.stub(); - const consentData = {}; // No apiVersion - mockGetConsentData.returns(consentData); - - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(789); - - manager.removeCmpEventListener(); - - const callArgs = mockCmpApi.getCall(0).args[0]; - expect(callArgs.apiVersion).to.equal(2); - }); - - it('should use default apiVersion when consent data is null', () => { - const mockCmpApi = sinon.stub(); - mockGetConsentData.returns(null); - - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(789); - - manager.removeCmpEventListener(); - - const callArgs = mockCmpApi.getCall(0).args[0]; - expect(callArgs.apiVersion).to.equal(2); - }); - - it('should not call CMP API when conditions are not met', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - // No listener ID set - - manager.removeCmpEventListener(); - - sinon.assert.notCalled(mockCmpApi); - sinon.assert.notCalled(utils.logInfo); - }); - }); - }); - - describe('GppCmpEventManager', () => { - let manager; - - beforeEach(() => { - manager = new GppCmpEventManager(); - }); - - describe('removeCmpEventListener', () => { - it('should call CMP API with GPP-specific params', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - manager.setCmpListenerId(321); - - manager.removeCmpEventListener(); - - sinon.assert.calledOnce(mockCmpApi); - sinon.assert.calledWith(utils.logInfo, 'Removing GPP CMP event listener'); - - const callArgs = mockCmpApi.getCall(0).args[0]; - expect(callArgs.command).to.equal('removeEventListener'); - expect(callArgs.parameter).to.equal(321); - expect(callArgs.apiVersion).to.be.undefined; // GPP doesn't set apiVersion - }); - - it('should not call CMP API when conditions are not met', () => { - const mockCmpApi = sinon.stub(); - manager.setCmpApi(mockCmpApi); - // No listener ID set - - manager.removeCmpEventListener(); - - sinon.assert.notCalled(mockCmpApi); - sinon.assert.notCalled(utils.logInfo); - }); - }); - }); - - describe('createCmpEventManager', () => { - it('should create TcfCmpEventManager for tcf type', () => { - const mockGetConsentData = sinon.stub(); - const manager = createCmpEventManager('tcf', mockGetConsentData); - - expect(manager).to.be.instanceOf(TcfCmpEventManager); - expect(manager.getConsentData).to.equal(mockGetConsentData); - }); - - it('should create TcfCmpEventManager without getConsentData function', () => { - const manager = createCmpEventManager('tcf'); - - expect(manager).to.be.instanceOf(TcfCmpEventManager); - expect(manager.getConsentData()).to.be.null; - }); - - it('should create GppCmpEventManager for gpp type', () => { - const manager = createCmpEventManager('gpp'); - - expect(manager).to.be.instanceOf(GppCmpEventManager); - }); - - it('should log error and return null for unknown type', () => { - const manager = createCmpEventManager('unknown'); - - expect(manager).to.be.null; - sinon.assert.calledWith(utils.logError, 'Unknown CMP type: unknown'); - }); - - it('should ignore getConsentData parameter for gpp type', () => { - const mockGetConsentData = sinon.stub(); - const manager = createCmpEventManager('gpp', mockGetConsentData); - - expect(manager).to.be.instanceOf(GppCmpEventManager); - // GPP manager doesn't use getConsentData - }); - }); -}); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index dfe5f3d6a0e..a033c56ddcd 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,4 @@ -import {consentConfig, gdprScope, resetConsentData, setConsentConfig, tcfCmpEventManager} from 'modules/consentManagementTcf.js'; +import {consentConfig, gdprScope, resetConsentData, setConsentConfig, } from 'modules/consentManagementTcf.js'; import {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; @@ -736,125 +736,6 @@ describe('consentManagement', function () { expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); }); - - it('should set CMP listener ID when listenerId is provided in tcfData', async function () { - const testConsentData = { - tcString: 'abc12345234', - gdprApplies: true, - purposeOneTreatment: false, - eventStatus: 'tcloaded', - listenerId: 123 - }; - - // Create a spy that will be applied when tcfCmpEventManager is created - let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData, true); - }); - - await setConsentConfig(goodConfig); - expect(await runHook()).to.be.true; - - sinon.assert.calledOnce(setCmpListenerIdSpy); - sinon.assert.calledWith(setCmpListenerIdSpy, 123); - - setCmpListenerIdSpy.restore(); - }); - - it('should not set CMP listener ID when listenerId is null', async function () { - const testConsentData = { - tcString: 'abc12345234', - gdprApplies: true, - purposeOneTreatment: false, - eventStatus: 'tcloaded', - listenerId: null - }; - - // Create a spy that will be applied when tcfCmpEventManager is created - let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData, true); - }); - - await setConsentConfig(goodConfig); - expect(await runHook()).to.be.true; - - sinon.assert.notCalled(setCmpListenerIdSpy); - - setCmpListenerIdSpy.restore(); - }); - - it('should not set CMP listener ID when listenerId is undefined', async function () { - const testConsentData = { - tcString: 'abc12345234', - gdprApplies: true, - purposeOneTreatment: false, - eventStatus: 'tcloaded', - listenerId: undefined - }; - - // Create a spy that will be applied when tcfCmpEventManager is created - let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData, true); - }); - - await setConsentConfig(goodConfig); - expect(await runHook()).to.be.true; - - sinon.assert.notCalled(setCmpListenerIdSpy); - setCmpListenerIdSpy.restore(); - }); - - it('should set CMP listener ID when listenerId is 0 (valid listener ID)', async function () { - const testConsentData = { - tcString: 'abc12345234', - gdprApplies: true, - purposeOneTreatment: false, - eventStatus: 'tcloaded', - listenerId: 0 - }; - - // Create a spy that will be applied when tcfCmpEventManager is created - let setCmpListenerIdSpy = sinon.spy(tcfCmpEventManager, 'setCmpListenerId'); - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData, true); - }); - - await setConsentConfig(goodConfig); - expect(await runHook()).to.be.true; - - sinon.assert.calledOnce(setCmpListenerIdSpy); - sinon.assert.calledWith(setCmpListenerIdSpy, 0); - setCmpListenerIdSpy.restore(); - }); - - it('should set CMP API reference when CMP is found', async function () { - const testConsentData = { - tcString: 'abc12345234', - gdprApplies: true, - purposeOneTreatment: false, - eventStatus: 'tcloaded' - }; - - // Create a spy that will be applied when tcfCmpEventManager is created - let setCmpApiSpy = sinon.spy(tcfCmpEventManager, 'setCmpApi'); - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData, true); - }); - - await setConsentConfig(goodConfig); - expect(await runHook()).to.be.true; - - sinon.assert.calledOnce(setCmpApiSpy); - expect(setCmpApiSpy.getCall(0).args[0]).to.be.a('function'); - setCmpApiSpy.restore(); - }); }); }); }); From ff286ec81cbac754b6c4df89a3f980b065d25331 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 23 Oct 2025 17:25:15 +0200 Subject: [PATCH 057/147] Adagio multi modules: placement params (#13857) (#14000) --- modules/adagioAnalyticsAdapter.js | 7 +- modules/adagioBidAdapter.js | 17 ++- modules/adagioRtdProvider.js | 76 +++++------ .../modules/adagioAnalyticsAdapter_spec.js | 118 ++++++++++++++---- test/spec/modules/adagioRtdProvider_spec.js | 34 ++--- 5 files changed, 170 insertions(+), 82 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index fd667799064..1c3241ef19d 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -267,7 +267,7 @@ function handlerAuctionInit(event) { ban_szs: bannerSizes.join(','), bdrs: sortedBidderNames.join(','), pgtyp: deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.pagetype', null), - plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.placement', null), + plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.adg_rtd.placement', null), // adg_rtd.placement is set by AdagioRtdProvider. t_n: adgRtdSession.testName || null, t_v: adgRtdSession.testVersion || null, s_id: adgRtdSession.id || null, @@ -289,6 +289,11 @@ function handlerAuctionInit(event) { // for backward compatibility: if we didn't find organizationId & site but we have a bid from adagio we might still find it in params qp.org_id = qp.org_id || adagioAdUnitBids[0].params.organizationId; qp.site = qp.site || adagioAdUnitBids[0].params.site; + + // `qp.plcmt` uses the value set by the AdagioRtdProvider. If not present, we fallback on the value set at the adUnit.params level. + if (!qp.plcmt) { + qp.plcmt = deepAccess(adagioAdUnitBids[0], 'params.placement', null); + } } } diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 0cd9ef6ec8a..0849b73c3f3 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -400,11 +400,18 @@ function autoFillParams(bid) { bid.params.site = adgGlobalConf.siteId.split(':')[1]; } - // `useAdUnitCodeAsPlacement` is an edge case. Useful when a Prebid Manager cannot handle properly params setting. - // In Prebid.js 9, `placement` should be defined in ortb2Imp and the `useAdUnitCodeAsPlacement` param should be removed - bid.params.placement = deepAccess(bid, 'ortb2Imp.ext.data.placement', bid.params.placement); - if (!bid.params.placement && (adgGlobalConf.useAdUnitCodeAsPlacement === true || bid.params.useAdUnitCodeAsPlacement === true)) { - bid.params.placement = bid.adUnitCode; + if (!bid.params.placement) { + let p = deepAccess(bid, 'ortb2Imp.ext.data.adg_rtd.placement', ''); + if (!p) { + // Use ortb2Imp.ext.data.placement for backward compatibility. + p = deepAccess(bid, 'ortb2Imp.ext.data.placement', ''); + } + + // `useAdUnitCodeAsPlacement` is an edge case. Useful when a Prebid Manager cannot handle properly params setting. + if (!p && bid.params.useAdUnitCodeAsPlacement === true) { + p = bid.adUnitCode; + } + bid.params.placement = p; } bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.divId', bid.params.adUnitElementId); diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index b30be4daf99..dfc6361234e 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -49,7 +49,7 @@ export const PLACEMENT_SOURCES = { }; export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); -const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); +const { logError, logInfo, logWarn } = prefixLog('AdagioRtdProvider:'); // Guard to avoid storing the same bid data several times. const guard = new Set(); @@ -240,6 +240,25 @@ export const _internal = { return value; } }); + }, + + // Compute the placement from the legacy RTD config params or ortb2Imp.ext.data.placement key. + computePlacementFromLegacy: function(rtdConfig, adUnit) { + const placementSource = deepAccess(rtdConfig, 'params.placementSource', ''); + let placementFromSource = ''; + + switch (placementSource.toLowerCase()) { + case PLACEMENT_SOURCES.ADUNITCODE: + placementFromSource = adUnit.code; + break; + case PLACEMENT_SOURCES.GPID: + placementFromSource = deepAccess(adUnit, 'ortb2Imp.ext.gpid') + break; + } + + const placementLegacy = deepAccess(adUnit, 'ortb2Imp.ext.data.placement', ''); + + return placementFromSource || placementLegacy; } }; @@ -319,7 +338,6 @@ function onBidRequest(bidderRequest, config, _userConsent) { * @param {*} config */ function onGetBidRequestData(bidReqConfig, callback, config) { - const configParams = deepAccess(config, 'params', {}); const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const features = _internal.getFeatures().get(); const ext = { @@ -347,30 +365,11 @@ function onGetBidRequestData(bidReqConfig, callback, config) { const slotPosition = getSlotPosition(divId); deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition); - // It is expected that the publisher set a `adUnits[].ortb2Imp.ext.data.placement` value. - // Btw, We allow fallback sources to programmatically set this value. - // The source is defined in the `config.params.placementSource` and the possible values are `code` or `gpid`. - // (Please note that this `placement` is not related to the oRTB video property.) - if (!deepAccess(ortb2Imp, 'ext.data.placement')) { - const { placementSource = '' } = configParams; - - switch (placementSource.toLowerCase()) { - case PLACEMENT_SOURCES.ADUNITCODE: - deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); - break; - case PLACEMENT_SOURCES.GPID: - deepSetValue(ortb2Imp, 'ext.data.placement', deepAccess(ortb2Imp, 'ext.gpid')); - break; - default: - logWarn('`ortb2Imp.ext.data.placement` is missing and `params.definePlacement` is not set in the config.'); - } - } - - // We expect that `pagetype`, `category`, `placement` are defined in FPD `ortb2.site.ext.data` and `adUnits[].ortb2Imp.ext.data` objects. - // Btw, we have to ensure compatibility with publishers that use the "legacy" adagio params at the adUnit.params level. const adagioBid = adUnit.bids.find(bid => _internal.isAdagioBidder(bid.bidder)); if (adagioBid) { // ortb2 level + // We expect that `pagetype`, `category` are defined in FPD `ortb2.site.ext.data` object. + // Btw, we still ensure compatibility with publishers that use the adagio params at the adUnit.params level. let mustWarnOrtb2 = false; if (!deepAccess(ortb2Site, 'ext.data.pagetype') && adagioBid.params.pagetype) { deepSetValue(ortb2Site, 'ext.data.pagetype', adagioBid.params.pagetype); @@ -380,21 +379,28 @@ function onGetBidRequestData(bidReqConfig, callback, config) { deepSetValue(ortb2Site, 'ext.data.category', adagioBid.params.category); mustWarnOrtb2 = true; } - - // ortb2Imp level - let mustWarnOrtb2Imp = false; - if (!deepAccess(ortb2Imp, 'ext.data.placement')) { - if (adagioBid.params.placement) { - deepSetValue(ortb2Imp, 'ext.data.placement', adagioBid.params.placement); - mustWarnOrtb2Imp = true; - } + if (mustWarnOrtb2) { + logInfo('`pagetype` and/or `category` have been set in the FPD `ortb2.site.ext.data` object from `adUnits[].bids.adagio.params`.'); } - if (mustWarnOrtb2) { - logWarn('`pagetype` and `category` must be defined in the FPD `ortb2.site.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.'); + // ortb2Imp level to handle legacy. + // The `placement` is finally set at the adUnit.params level (see https://github.com/prebid/Prebid.js/issues/12845) + // but we still need to set it at the ortb2Imp level for our internal use. + const placementParam = adagioBid.params.placement; + const adgRtdPlacement = deepAccess(ortb2Imp, 'ext.data.adg_rtd.placement', ''); + + if (placementParam) { + // Always overwrite the ortb2Imp value with the one from the adagio adUnit.params.placement if defined. + // This is the common case. + deepSetValue(ortb2Imp, 'ext.data.adg_rtd.placement', placementParam); } - if (mustWarnOrtb2Imp) { - logWarn('`placement` must be defined in the FPD `adUnits[].ortb2Imp.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.'); + + if (!placementParam && !adgRtdPlacement) { + const p = _internal.computePlacementFromLegacy(config, adUnit); + if (p) { + deepSetValue(ortb2Imp, 'ext.data.adg_rtd.placement', p); + logWarn('`ortb2Imp.ext.data.adg_rtd.placement` has been set from a legacy source. Please set `bids[].adagio.params.placement` or `ortb2Imp.ext.data.adg_rtd.placement` value.'); + } } } }); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 9178d7b532d..d77a0fb8bd6 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -4,6 +4,7 @@ import adagioAnalyticsAdapter, { _internal } from 'modules/adagioAnalyticsAdapte import { EVENTS } from 'src/constants.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; +import { deepClone } from 'src/utils.js'; const adapterManager = require('src/adapterManager').default; const events = require('src/events'); @@ -191,8 +192,10 @@ const BID_CACHED = Object.assign({}, BID_ADAGIO, { latestTargetedAuctionId: BID_ADAGIO.auctionId, }); +const PARAMS_PLCMT = 'placement_from_params'; const PARAMS_ADG = { environment: 'desktop', + placement: PARAMS_PLCMT, }; const ORTB_DATA = { @@ -208,6 +211,13 @@ const ADG_RTD = { } }; +const ORTB2IMP_PLCMT = 'placement_from_ortb2imp'; +const ORTB2IMP_DATA_ADG = { + 'adg_rtd': { + 'placement': ORTB2IMP_PLCMT + } +}; + const AUCTION_INIT_ANOTHER = { 'auctionId': AUCTION_ID, 'timestamp': 1519767010567, @@ -228,6 +238,11 @@ const AUCTION_INIT_ANOTHER = { ] } }, + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, 'sizes': [[640, 480]], 'bids': [ { 'bidder': 'another', @@ -250,14 +265,7 @@ const AUCTION_INIT_ANOTHER = { 'publisherId': '1001' }, }, ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'ortb2Imp': { - 'ext': { - 'data': { - 'placement': 'pave_top', - } - } - }, + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' }, { 'code': '/19968336/footer-bid-tag-1', 'mediaTypes': { @@ -281,7 +289,9 @@ const AUCTION_INIT_ANOTHER = { 'ortb2Imp': { 'ext': { 'data': { - 'placement': 'pave_top', + 'adg_rtd': { + 'placement': 'footer' + } } } }, @@ -303,6 +313,11 @@ const AUCTION_INIT_ANOTHER = { 'sizes': [[640, 480]] } }, + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, 'adUnitCode': '/19968336/header-bid-tag-1', 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], @@ -363,6 +378,12 @@ const AUCTION_INIT_ANOTHER = { 'sizes': [[640, 480]] } }, + + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, 'adUnitCode': '/19968336/header-bid-tag-1', 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], @@ -405,6 +426,11 @@ const AUCTION_INIT_ANOTHER = { 'sizes': [[640, 480]] } }, + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, 'adUnitCode': '/19968336/header-bid-tag-1', 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], @@ -453,7 +479,12 @@ const AUCTION_INIT_ANOTHER = { 'bidderRequestId': '1be65d7958826a', 'auctionId': AUCTION_ID, 'src': 'client', - 'bidRequestsCount': 1 + 'bidRequestsCount': 1, + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, } ], 'timeout': 3000, @@ -514,9 +545,7 @@ const AUCTION_INIT_CACHE = { 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'ortb2Imp': { 'ext': { - 'data': { - 'placement': 'pave_top', - } + 'data': ORTB2IMP_DATA_ADG } }, }, { @@ -539,13 +568,6 @@ const AUCTION_INIT_CACHE = { }, } ], 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'ortb2Imp': { - 'ext': { - 'data': { - 'placement': 'pave_top', - } - } - }, } ], 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], 'bidderRequests': [ { @@ -562,6 +584,11 @@ const AUCTION_INIT_CACHE = { 'sizes': [[640, 480]] } }, + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, 'adUnitCode': '/19968336/header-bid-tag-1', 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], @@ -621,6 +648,11 @@ const AUCTION_INIT_CACHE = { 'sizes': [[640, 480]] } }, + 'ortb2Imp': { + 'ext': { + 'data': ORTB2IMP_DATA_ADG + } + }, 'adUnitCode': '/19968336/header-bid-tag-1', 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], @@ -818,7 +850,7 @@ describe('adagio analytics adapter', () => { expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); expect(search.pgtyp).to.equal('article'); - expect(search.plcmt).to.equal('pave_top'); + expect(search.plcmt).to.equal(ORTB2IMP_PLCMT); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); @@ -869,6 +901,7 @@ describe('adagio analytics adapter', () => { expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.win_bdr).to.equal('another'); + expect(search.plcmt).to.equal(ORTB2IMP_PLCMT); expect(search.win_mt).to.equal('ban'); expect(search.win_ban_sz).to.equal('728x90'); expect(search.win_net_cpm).to.equal('2.052'); @@ -877,6 +910,43 @@ describe('adagio analytics adapter', () => { } }); + it('it fallback on the adUnit.params.placement value if adg_rtd.placement is not set', () => { + const mockAuctionInit = deepClone(MOCK.AUCTION_INIT.another); + for (const adUnit of mockAuctionInit.adUnits) { + delete adUnit.ortb2Imp?.ext?.data.adg_rtd; + } + for (const bidRequest of mockAuctionInit.bidderRequests) { + for (const bid of bidRequest.bids) { + delete bid.ortb2Imp?.ext?.data.adg_rtd; + } + } + + events.emit(EVENTS.AUCTION_INIT, mockAuctionInit); + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.s_id).to.equal(SESSION_ID); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.org_id).to.equal('1001'); + expect(search.site).to.equal('test-com'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.url_dmn).to.equal(window.location.hostname); + expect(search.pgtyp).to.equal('article'); + expect(search.plcmt).to.equal(PARAMS_PLCMT); + expect(search.mts).to.equal('ban'); + expect(search.ban_szs).to.equal('640x100,640x480'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); + expect(search.bdrs_timeout).to.not.exist; + expect(search.adg_mts).to.equal('ban'); + } + }); + it('builds and sends auction data with a cached bid win', () => { events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); @@ -903,7 +973,7 @@ describe('adagio analytics adapter', () => { expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); expect(search.pgtyp).to.equal('article'); - expect(search.plcmt).to.equal('pave_top'); + expect(search.plcmt).to.equal(ORTB2IMP_PLCMT); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another'); @@ -928,7 +998,7 @@ describe('adagio analytics adapter', () => { expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); expect(search.pgtyp).to.equal('article'); - expect(search.plcmt).to.equal('pave_top'); + expect(search.plcmt).to.be.undefined; // no placement set, no adagio bidder for this adUnit. expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x480'); expect(search.bdrs).to.equal('another'); @@ -951,7 +1021,7 @@ describe('adagio analytics adapter', () => { expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); expect(search.pgtyp).to.equal('article'); - expect(search.plcmt).to.equal('pave_top'); + expect(search.plcmt).to.equal(ORTB2IMP_PLCMT); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index 84a726d2cb2..3ac2d1fa73f 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -507,7 +507,7 @@ describe('Adagio Rtd Provider', function () { expect(ortb2ImpExt.adunit_position).equal(''); }); - describe('update the ortb2Imp.ext.data.placement if not present', function() { + describe('set the ortb2Imp.ext.data.adg_rtd.placement', function() { const config = { name: SUBMODULE_NAME, params: { @@ -516,50 +516,50 @@ describe('Adagio Rtd Provider', function () { } }; - it('update the placement value with the adUnit.code value', function() { + it('set the adg_rtd.placement value from the adUnit[].bids adagio.params.placement value', function() { + const placement = 'placement-value'; + const configCopy = utils.deepClone(config); - configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; const bidRequest = utils.deepClone(bidReqConfig); + bidRequest.adUnits[0].bids[0].params.placement = placement; adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); - expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.placement).to.equal(placement); }); - it('update the placement value with the gpid value', function() { + it('fallback on the adUnit.code value to set the adg_rtd.placement value', function() { const configCopy = utils.deepClone(config); - configCopy.params.placementSource = PLACEMENT_SOURCES.GPID; + configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; const bidRequest = utils.deepClone(bidReqConfig); - const gpid = '/19968336/header-bid-tag-0' - utils.deepSetValue(bidRequest.adUnits[0], 'ortb2Imp.ext.gpid', gpid) adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); - expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(gpid); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.placement).to.equal('div-gpt-ad-1460505748561-0'); }); - it('update the placement value the legacy adUnit[].bids adagio.params.placement value', function() { - const placement = 'placement-value'; - + it('fallback on the the gpid value to set the adg_rtd.placement value ', function() { const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.GPID; const bidRequest = utils.deepClone(bidReqConfig); - bidRequest.adUnits[0].bids[0].params.placement = placement; + const gpid = '/19968336/header-bid-tag-0' + utils.deepSetValue(bidRequest.adUnits[0], 'ortb2Imp.ext.gpid', gpid) adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); - expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(placement); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.placement).to.equal(gpid); }); - it('it does not populate `ortb2Imp.ext.data.placement` if no fallback', function() { + it('it does not populate `ortb2Imp.ext.data.adg_rtd.placement` if no fallback', function() { const configCopy = utils.deepClone(config); const bidRequest = utils.deepClone(bidReqConfig); adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); - expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.not.exist; + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.placement).to.not.exist; }); it('ensure we create the `ortb2Imp` object if it does not exist', function() { @@ -571,7 +571,7 @@ describe('Adagio Rtd Provider', function () { adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); - expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.placement).to.equal('div-gpt-ad-1460505748561-0'); }); }); }); From 3dafd05fc3be1ac66140f5177f3b51671cbf09e2 Mon Sep 17 00:00:00 2001 From: jkneiphof <64132960+jkneiphof@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:34:39 +0200 Subject: [PATCH 058/147] WelectBidAdapter: add `mediaType` param to bid response (#14046) --- modules/welectBidAdapter.js | 1 + test/spec/modules/welectBidAdapter_spec.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js index b271679fc4f..ea2600247c3 100644 --- a/modules/welectBidAdapter.js +++ b/modules/welectBidAdapter.js @@ -119,6 +119,7 @@ export const spec = { ttl: responseBody.bidResponse.ttl, ad: responseBody.bidResponse.ad, vastUrl: responseBody.bidResponse.vastUrl, + mediaType: responseBody.bidResponse.mediaType, meta: { advertiserDomains: responseBody.bidResponse.meta.advertiserDomains } diff --git a/test/spec/modules/welectBidAdapter_spec.js b/test/spec/modules/welectBidAdapter_spec.js index a0e3c797ac9..b9abf3613a2 100644 --- a/test/spec/modules/welectBidAdapter_spec.js +++ b/test/spec/modules/welectBidAdapter_spec.js @@ -178,7 +178,8 @@ describe('WelectAdapter', function () { ttl: 120, vastUrl: 'some vast url', height: 640, - width: 320 + width: 320, + mediaType: 'video' } } } @@ -213,7 +214,8 @@ describe('WelectAdapter', function () { requestId: 'some bid id', ttl: 120, vastUrl: 'some vast url', - width: 320 + width: 320, + mediaType: 'video' } it('if response reflects unavailability, should be empty', function () { From 652f724bd4fa22878001807709df03d521d3acff Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Thu, 23 Oct 2025 18:35:22 +0300 Subject: [PATCH 059/147] Adkernel Bid Adapter: add Qohere alias (#14064) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 3bfaeeff712..c6ccb8053e0 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -105,7 +105,8 @@ export const spec = { {code: 'pixelpluses', gvlid: 1209}, {code: 'urekamedia'}, {code: 'smartyexchange'}, - {code: 'infinety'} + {code: 'infinety'}, + {code: 'qohere'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 8a8379165eaaf4ac7a2c7e23bd655799550458ca Mon Sep 17 00:00:00 2001 From: Stav Ben Shlomo <137684171+StavBenShlomoBrowsi@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:26:21 +0300 Subject: [PATCH 060/147] browsiRtdProvider: do not init analytics module (#13883) * browsiRtdProvider-analytics-module * browsiRtdProvider-analytics-module * Update browsiRtdProvider.js --------- Co-authored-by: Stav Ben Shlomo Co-authored-by: Patrick McCann Co-authored-by: Patrick McCann --- modules/browsiRtdProvider.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 7d5611b741c..ac1d2147f3b 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -64,13 +64,6 @@ export function setTimestamp() { TIMESTAMP = timestamp(); } -export function initAnalytics() { - getGlobal().enableAnalytics({ - provider: 'browsi', - options: {} - }) -} - export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { @@ -426,7 +419,6 @@ function init(moduleConfig) { _moduleParams = moduleConfig.params; _moduleParams.siteKey = moduleConfig.params.siteKey || moduleConfig.params.sitekey; _moduleParams.pubKey = moduleConfig.params.pubKey || moduleConfig.params.pubkey; - initAnalytics(); setTimestamp(); if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { sendModuleInitEvent(); From a509f178adcdfc457580fd1ecb5dcfcd006bb023 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Thu, 23 Oct 2025 14:31:52 -0600 Subject: [PATCH 061/147] pgamssp Bid Adapter : update deleted gvlid (#14065) * Update pgamsspBidAdapter.js no longer valid gvlid * fix gvlid --- modules/pgamsspBidAdapter.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index 36dbd1159cc..859bfc9de7e 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -3,13 +3,11 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'pgamssp'; -const GVLID = 1353; const AD_URL = 'https://us-east.pgammedia.com/pbjs'; const SYNC_URL = 'https://cs.pgammedia.com'; export const spec = { code: BIDDER_CODE, - gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(), From 2e06eca0e63c07eaa96b6bc8ad151562a4c2e4c8 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 Oct 2025 22:43:13 +0000 Subject: [PATCH 062/147] Prebid 10.14.0 release --- .../gpt/x-domain/creative.html | 2 +- metadata/modules.json | 16 ++++++++- 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 | 15 +++++--- 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/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/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 | 10 +++--- metadata/modules/appushBidAdapter.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/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 | 36 +++++++++++++++++++ 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/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/performaxBidAdapter.json | 2 +- metadata/modules/pgamsspBidAdapter.json | 11 ++---- 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/revcontentBidAdapter.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/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 | 4 +-- package.json | 2 +- 269 files changed, 347 insertions(+), 295 deletions(-) create mode 100644 metadata/modules/defineMediaBidAdapter.json diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 967147b34ba..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' - +

Prebid Test Bidder Example

+

+

Banner ad
- \ No newline at end of file + diff --git a/libraries/adagioUtils/adagioUtils.js b/libraries/adagioUtils/adagioUtils.js index c2614c45d0c..265a442017a 100644 --- a/libraries/adagioUtils/adagioUtils.js +++ b/libraries/adagioUtils/adagioUtils.js @@ -22,6 +22,7 @@ export const _ADAGIO = (function() { const w = getBestWindowForAdagio(); w.ADAGIO = w.ADAGIO || {}; + // TODO: consider using the Prebid-generated page view ID instead of generating a custom one w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; diff --git a/libraries/pbsExtensions/processors/pageViewIds.js b/libraries/pbsExtensions/processors/pageViewIds.js new file mode 100644 index 00000000000..c71d32b7735 --- /dev/null +++ b/libraries/pbsExtensions/processors/pageViewIds.js @@ -0,0 +1,9 @@ +import {deepSetValue} from '../../../src/utils.js'; + +export function setRequestExtPrebidPageViewIds(ortbRequest, bidderRequest) { + deepSetValue( + ortbRequest, + `ext.prebid.page_view_ids.${bidderRequest.bidderCode}`, + bidderRequest.pageViewId + ); +} diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js index 9346334abdb..3fa97ae674b 100644 --- a/libraries/pbsExtensions/processors/pbs.js +++ b/libraries/pbsExtensions/processors/pbs.js @@ -7,6 +7,7 @@ import {setImpAdUnitCode} from './adUnitCode.js'; import {setRequestExtPrebid, setRequestExtPrebidChannel} from './requestExtPrebid.js'; import {setBidResponseVideoCache} from './video.js'; import {addEventTrackers} from './eventTrackers.js'; +import {setRequestExtPrebidPageViewIds} from './pageViewIds.js'; export const PBS_PROCESSORS = { [REQUEST]: { @@ -21,7 +22,11 @@ export const PBS_PROCESSORS = { extPrebidAliases: { // sets ext.prebid.aliases fn: setRequestExtPrebidAliases - } + }, + extPrebidPageViewIds: { + // sets ext.prebid.page_view_ids + fn: setRequestExtPrebidPageViewIds + }, }, [IMP]: { params: { diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js index 9c8975542eb..ab1fb8b606c 100644 --- a/modules/carodaBidAdapter.js +++ b/modules/carodaBidAdapter.js @@ -40,6 +40,7 @@ export const spec = { ); }, buildRequests: (validBidRequests, bidderRequest) => { + // TODO: consider using the Prebid-generated page view ID instead of generating a custom one topUsableWindow.carodaPageViewId = topUsableWindow.carodaPageViewId || Math.floor(Math.random() * 1e9); const pageViewId = topUsableWindow.carodaPageViewId; const ortbCommon = getORTBCommon(bidderRequest); diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index a656dee0fc1..875dfdedf67 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -2,7 +2,6 @@ import { registerBidder } from "../src/adapters/bidderFactory.js"; import { getStorageManager } from "../src/storageManager.js"; import { BANNER } from "../src/mediaTypes.js"; import { - generateUUID, getParameterByName, isNumber, logError, @@ -27,11 +26,6 @@ export const BID_ENDPOINT = "https://prebid.cwi.re/v1/bid"; export const EVENT_ENDPOINT = "https://prebid.cwi.re/v1/event"; export const GVL_ID = 1081; -/** - * Allows limiting ad impressions per site render. Unique per prebid instance ID. - */ -export const pageViewId = generateUUID(); - export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); /** @@ -248,7 +242,7 @@ export const spec = { slots: processed, httpRef: referrer, // TODO: Verify whether the auctionId and the usage of pageViewId make sense. - pageViewId: pageViewId, + pageViewId: bidderRequest.pageViewId, networkBandwidth: getConnectionDownLink(window.navigator), sdk: { version: "$prebid.version$", diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 1146ea77692..1909112d36d 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -172,6 +172,7 @@ function buildRequests(validBidRequests, bidderRequest) { const page = {} if (validPageId) { + // TODO: consider using the Prebid-generated page view ID instead of generating a custom one page.id = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID); } if (validPageTimestamp) { diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index fcb9637f166..2b0c55b2fb9 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,6 +1,5 @@ import { deepAccess, - generateUUID, getWindowSelf, isArray, isStr, @@ -15,8 +14,6 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j const additionalData = new WeakMap(); -export const pageViewId = generateUUID(); - export function setAdditionalData(obj, key, value) { const prevValue = additionalData.get(obj) || {}; additionalData.set(obj, { ...prevValue, [key]: value }); @@ -185,7 +182,7 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { kobler: { tcf_purpose_2_given: purpose2Given, tcf_purpose_3_given: purpose3Given, - page_view_id: pageViewId + page_view_id: bidderRequest.pageViewId } } }; diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js index 22b8476ef54..2bcacd92dd9 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -22,6 +22,7 @@ const prebidVersion = '$prebid.version$' const analyticsType = 'endpoint' const ADAPTER_CODE = 'oolo' const AUCTION_END_SEND_TIMEOUT = 1500 +// TODO: consider using the Prebid-generated page view ID instead of generating a custom one export const PAGEVIEW_ID = +generatePageViewId() const { diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 7bf1602e5e7..ab9e18e6837 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -27,10 +27,10 @@ const BIDDER_SPECIFIC_REQUEST_PROPS = new Set(['bidderCode', 'bidderRequestId', const getMinimumFloor = (() => { const getMin = minimum(currencyCompare(floor => [floor.bidfloor, floor.bidfloorcur])); return function(candidates) { - let min; + let min = null; for (const candidate of candidates) { if (candidate?.bidfloorcur == null || candidate?.bidfloor == null) return null; - min = min == null ? candidate : getMin(min, candidate); + min = min === null ? candidate : getMin(min, candidate); } return min; } @@ -133,7 +133,7 @@ const PBS_CONVERTER = ortbConverter({ // also, take overrides from s2sConfig.adapterOptions const adapterOptions = context.s2sBidRequest.s2sConfig.adapterOptions; for (const req of context.actualBidRequests.values()) { - setImpBidParams(imp, req, context, context); + setImpBidParams(imp, req); if (adapterOptions && adapterOptions[req.bidder]) { Object.assign(imp.ext.prebid.bidder[req.bidder], adapterOptions[req.bidder]); } @@ -237,6 +237,10 @@ const PBS_CONVERTER = ortbConverter({ extPrebidAliases(orig, ortbRequest, proxyBidderRequest, context) { // override alias processing to do it for each bidder in the request context.actualBidderRequests.forEach(req => orig(ortbRequest, req, context)); + }, + extPrebidPageViewIds(orig, ortbRequest, proxyBidderRequest, context) { + // override page view ID processing to do it for each bidder in the request + context.actualBidderRequests.forEach(req => orig(ortbRequest, req, context)); } }, [RESPONSE]: { diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 937c597c46c..29534ae7318 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -18,7 +18,6 @@ const getConfig = config.getConfig; const storageManager = getStorageManager({bidderCode: BIDDER_CODE}); const refreshes = {}; const placementCounters = {}; -const pageViewId = generateUUID(); const pageViewStart = new Date().getTime(); let auctionCounter = 0; @@ -43,7 +42,7 @@ export const spec = { site: deepAccess(bidRequests, '0.params.site'), sessionId: getSessionId(), counter: auctionCounter++, - pageViewId: pageViewId, + pageViewId: bidderRequest.pageViewId, pageViewStart: pageViewStart, gdprConsent: gdprApplies === true ? hasFullGdprConsent(deepAccess(bidderRequest, 'gdprConsent')) : false, cur: getCurrencies(), diff --git a/src/adapterManager.ts b/src/adapterManager.ts index 17e845a765b..d19a43fcf03 100644 --- a/src/adapterManager.ts +++ b/src/adapterManager.ts @@ -67,6 +67,7 @@ import type { AnalyticsConfig, AnalyticsProvider, AnalyticsProviderConfig, } from "../libraries/analyticsAdapter/AnalyticsAdapter.ts"; +import {getGlobal} from "./prebidGlobal.ts"; export {gdprDataHandler, gppDataHandler, uspDataHandler, coppaDataHandler} from './consentHandler.js'; @@ -168,6 +169,7 @@ export interface BaseBidderRequest { */ bidderRequestId: Identifier; auctionId: Identifier; + pageViewId: Identifier; /** * The bidder associated with this request, or null in the case of stored impressions. */ @@ -554,6 +556,15 @@ const adapterManager = { return bidderRequest as T; } + const pbjsInstance = getGlobal(); + + function getPageViewIdForBidder(bidderCode: string | null): string { + if (!pbjsInstance.pageViewIdPerBidder.has(bidderCode)) { + pbjsInstance.pageViewIdPerBidder.set(bidderCode, generateUUID()); + } + return pbjsInstance.pageViewIdPerBidder.get(bidderCode); + } + _s2sConfigs.forEach(s2sConfig => { const s2sParams = s2sActivityParams(s2sConfig); if (s2sConfig && s2sConfig.enabled && dep.isAllowed(ACTIVITY_FETCH_BIDS, s2sParams)) { @@ -564,11 +575,13 @@ const adapterManager = { (serverBidders.length === 0 && hasModuleBids ? [null] : serverBidders).forEach(bidderCode => { const bidderRequestId = generateUUID(); + const pageViewId = getPageViewIdForBidder(bidderCode); const metrics = auctionMetrics.fork(); const bidderRequest = addOrtb2({ bidderCode, auctionId, bidderRequestId, + pageViewId, uniquePbsTid, bids: getBids({ bidderCode, @@ -611,10 +624,12 @@ const adapterManager = { const adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); clientBidders.forEach(bidderCode => { const bidderRequestId = generateUUID(); + const pageViewId = getPageViewIdForBidder(bidderCode); const metrics = auctionMetrics.fork(); const bidderRequest = addOrtb2({ bidderCode, auctionId, + pageViewId, bidderRequestId, bids: getBids({ bidderCode, diff --git a/src/prebid.ts b/src/prebid.ts index 571f4106feb..96b53e541af 100644 --- a/src/prebid.ts +++ b/src/prebid.ts @@ -102,6 +102,7 @@ declare module './prebidGlobal' { */ delayPrerendering?: boolean adUnits: AdUnitDefinition[]; + pageViewIdPerBidder: Map } } @@ -113,6 +114,7 @@ logInfo('Prebid.js v$prebid.version$ loaded'); // create adUnit array pbjsInstance.adUnits = pbjsInstance.adUnits || []; +pbjsInstance.pageViewIdPerBidder = pbjsInstance.pageViewIdPerBidder || new Map(); function validateSizes(sizes, targLength?: number) { let cleanSizes = []; @@ -483,6 +485,7 @@ declare module './prebidGlobal' { setBidderConfig: typeof config.setBidderConfig; processQueue: typeof processQueue; triggerBilling: typeof triggerBilling; + refreshPageViewId: typeof refreshPageViewId; } } @@ -1259,4 +1262,15 @@ function triggerBilling({adId, adUnitCode}: { } addApiMethod('triggerBilling', triggerBilling); +/** + * Refreshes the previously generated page view ID. Can be used to instruct bidders + * that use page view ID to consider future auctions as part of a new page load. + */ +function refreshPageViewId() { + for (const key of pbjsInstance.pageViewIdPerBidder.keys()) { + pbjsInstance.pageViewIdPerBidder.set(key, generateUUID()); + } +} +addApiMethod('refreshPageViewId', refreshPageViewId); + export default pbjsInstance; diff --git a/src/types/common.d.ts b/src/types/common.d.ts index b636a6cbabe..3f385ab6868 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -14,6 +14,12 @@ export type Currency = string; export type AdUnitCode = string; export type Size = [number, number]; export type ContextIdentifiers = { + /** + * Page view ID. Unique for a page view (one load of Prebid); can also be refreshed programmatically. + * Shared across all requests and responses within the page view, for the same bidder. + * Different bidders see a different page view ID. + */ + pageViewId: Identifier; /** * Auction ID. Unique for any given auction, but shared across all requests and responses within that auction. */ diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index 3cbf8a63125..1a514d33155 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -29,6 +29,9 @@ describe("C-WIRE bid adapter", () => { transactionId: "04f2659e-c005-4eb1-a57c-fa93145e3843", }, ]; + const bidderRequest = { + pageViewId: "326dca71-9ca0-4e8f-9e4d-6106161ac1ad" + } const response = { body: { cwid: "2ef90743-7936-4a82-8acf-e73382a64e94", @@ -67,7 +70,7 @@ describe("C-WIRE bid adapter", () => { }); describe("buildRequests", function () { it("sends bid request to ENDPOINT via POST", function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.url).to.equal(BID_ENDPOINT); expect(request.method).to.equal("POST"); }); @@ -89,7 +92,7 @@ describe("C-WIRE bid adapter", () => { // set from bid.params const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); expect(payload.cwcreative).to.exist; expect(payload.cwcreative).to.deep.equal("str-str"); @@ -110,7 +113,7 @@ describe("C-WIRE bid adapter", () => { it("width and height should be set", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); const el = document.getElementById(`${bidRequest.adUnitCode}`); @@ -142,7 +145,7 @@ describe("C-WIRE bid adapter", () => { it("css maxWidth should be set", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); const el = document.getElementById(`${bidRequest.adUnitCode}`); @@ -167,7 +170,7 @@ describe("C-WIRE bid adapter", () => { it("read from url parameter", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); logInfo(JSON.stringify(payload)); @@ -190,7 +193,7 @@ describe("C-WIRE bid adapter", () => { it("read from url parameter", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); logInfo(JSON.stringify(payload)); @@ -213,7 +216,7 @@ describe("C-WIRE bid adapter", () => { it("read from url parameter", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); logInfo(JSON.stringify(payload)); @@ -238,7 +241,7 @@ describe("C-WIRE bid adapter", () => { it("cw_id is set", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); logInfo(JSON.stringify(payload)); @@ -263,7 +266,7 @@ describe("C-WIRE bid adapter", () => { it("pageId flattened", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); logInfo(JSON.stringify(payload)); @@ -305,7 +308,7 @@ describe("C-WIRE bid adapter", () => { it("build request adds pageId", function () { const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); expect(payload.slots[0].pageId).to.exist; @@ -392,7 +395,7 @@ describe("C-WIRE bid adapter", () => { sandbox.stub(autoplayLib, "isAutoplayEnabled").returns(true); const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); expect(payload.slots[0].params.autoplay).to.equal(true); @@ -402,7 +405,7 @@ describe("C-WIRE bid adapter", () => { sandbox.stub(autoplayLib, "isAutoplayEnabled").returns(false); const bidRequest = deepClone(bidRequests[0]); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); expect(payload.slots[0].params.autoplay).to.equal(false); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 187ccf9459f..4e70ac6f9f3 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {pageViewId, spec} from 'modules/koblerBidAdapter.js'; +import {spec} from 'modules/koblerBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -7,7 +7,7 @@ import {getRefererInfo} from 'src/refererDetection.js'; import { setConfig as setCurrencyConfig } from '../../../modules/currency.js'; import { addFPDToBidderRequest } from '../../helpers/fpd.js'; -function createBidderRequest(auctionId, timeout, pageUrl, gdprVendorData = {}) { +function createBidderRequest(auctionId, timeout, pageUrl, gdprVendorData = {}, pageViewId) { const gdprConsent = { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', apiVersion: 2, @@ -21,7 +21,8 @@ function createBidderRequest(auctionId, timeout, pageUrl, gdprVendorData = {}) { refererInfo: { page: pageUrl || 'example.com' }, - gdprConsent: gdprConsent + gdprConsent: gdprConsent, + pageViewId }; } @@ -259,15 +260,17 @@ describe('KoblerAdapter', function () { const testUrl = 'kobler.no'; const auctionId1 = '8319af54-9795-4642-ba3a-6f57d6ff9100'; const auctionId2 = 'e19f2d0c-602d-4969-96a1-69a22d483f47'; + const pageViewId1 = '2949ce3c-2c4d-4b96-9ce0-8bf5aa0bb416'; + const pageViewId2 = '6c449b7d-c9b0-461d-8cc7-ce0a8da58349'; const timeout = 5000; const validBidRequests = [createValidBidRequest()]; - const bidderRequest1 = createBidderRequest(auctionId1, timeout, testUrl); - const bidderRequest2 = createBidderRequest(auctionId2, timeout, testUrl); + const bidderRequest1 = createBidderRequest(auctionId1, timeout, testUrl, {}, pageViewId1); + const bidderRequest2 = createBidderRequest(auctionId2, timeout, testUrl, {}, pageViewId2); const openRtbRequest1 = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest1).data); - expect(openRtbRequest1.ext.kobler.page_view_id).to.be.equal(pageViewId); + expect(openRtbRequest1.ext.kobler.page_view_id).to.be.equal(pageViewId1); const openRtbRequest2 = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest2).data); - expect(openRtbRequest2.ext.kobler.page_view_id).to.be.equal(pageViewId); + expect(openRtbRequest2.ext.kobler.page_view_id).to.be.equal(pageViewId2); }); it('should read data from valid bid requests', function () { @@ -440,6 +443,7 @@ describe('KoblerAdapter', function () { }); it('should create whole OpenRTB request', function () { + const pageViewId = 'aa9f0b20-a642-4d0e-acb5-e35805253ef7'; const validBidRequests = [ createValidBidRequest( { @@ -483,7 +487,8 @@ describe('KoblerAdapter', function () { } } } - } + }, + pageViewId ); const result = spec.buildRequests(validBidRequests, bidderRequest); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 4173cd88b1b..882f38ba2a9 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3,8 +3,7 @@ import { PrebidServer as Adapter, resetSyncedStatus, validateConfig, - s2sDefaultConfig, - processPBSRequest + s2sDefaultConfig } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager, {PBS_ADAPTER_NAME} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; @@ -626,6 +625,7 @@ describe('S2S Adapter', function () { 'auctionId': '173afb6d132ba3', 'bidderRequestId': '3d1063078dfcc8', 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'pageViewId': '84dfd20f-0a5a-4ac6-a86b-91569066d4f4', 'bids': [ { 'bidder': 'appnexus', @@ -2384,7 +2384,7 @@ describe('S2S Adapter', function () { expect(requestBid.ext.prebid.targeting.includewinners).to.equal(true); }); - it('adds s2sConfig video.ext.prebid to request for ORTB', function () { + it('adds custom property in s2sConfig.extPrebid to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { extPrebid: { foo: 'bar' @@ -2415,7 +2415,7 @@ describe('S2S Adapter', function () { }); }); - it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { + it('overrides request.ext.prebid properties using s2sConfig.extPrebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { extPrebid: { targeting: { @@ -2448,7 +2448,7 @@ describe('S2S Adapter', function () { }); }); - it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { + it('overrides request.ext.prebid properties and adds custom property from s2sConfig.extPrebid for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { extPrebid: { cache: { @@ -2703,6 +2703,21 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.ext.prebid.multibid).to.deep.equal(expected); }); + it('passes page view IDs per bidder in request', function () { + const clonedBidRequest = utils.deepClone(BID_REQUESTS[0]); + clonedBidRequest.bidderCode = 'some-other-bidder'; + clonedBidRequest.pageViewId = '490a1cbc-a03c-429a-b212-ba3649ca820c'; + const bidRequests = [BID_REQUESTS[0], clonedBidRequest]; + const expected = { + appnexus: '84dfd20f-0a5a-4ac6-a86b-91569066d4f4', + 'some-other-bidder': '490a1cbc-a03c-429a-b212-ba3649ca820c' + }; + + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.page_view_ids).to.deep.equal(expected); + }); + it('sets and passes pbjs version in request if channel does not exist in s2sConfig', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); diff --git a/test/spec/ortbConverter/pbsExtensions/params_spec.js b/test/spec/ortbConverter/pbsExtensions/params_spec.js index d1b36c18b49..bad5307b9af 100644 --- a/test/spec/ortbConverter/pbsExtensions/params_spec.js +++ b/test/spec/ortbConverter/pbsExtensions/params_spec.js @@ -1,20 +1,9 @@ import {setImpBidParams} from '../../../../libraries/pbsExtensions/processors/params.js'; describe('pbjs -> ortb bid params to imp[].ext.prebid.BIDDER', () => { - let bidderRegistry, index, adUnit; - beforeEach(() => { - bidderRegistry = {}; - adUnit = {code: 'mockAdUnit'}; - index = { - getAdUnit() { - return adUnit; - } - } - }); - - function setParams(bidRequest, context, deps = {}) { + function setParams(bidRequest = {}) { const imp = {}; - setImpBidParams(imp, bidRequest, context, Object.assign({bidderRegistry, index}, deps)) + setImpBidParams(imp, bidRequest) return imp; } From 79e6dbf0d957ff60922c7b1cc2f1456042fd2e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petric=C4=83=20Nanc=C4=83?= Date: Mon, 10 Nov 2025 13:56:50 +0200 Subject: [PATCH 093/147] sevio Bid Adapter : add extra parameters required by the BE (#13904) * Add extra parameters required by the BE * Correct prod endpoint * Removed properties that were flagged as fingerprinting * Added tests for the additions made and also some extra safety checks in the adapter * Removed networkBandwidth and networkQuality as they are flagged as fingerprinting * Placed getDomComplexity under the fpdUtils/pageInfo.js * Use the getPageDescription and getPagetitle from the fpdUtils * Remove the tests for the params that are not sent anymore --- libraries/fpdUtils/pageInfo.js | 9 ++ modules/sevioBidAdapter.js | 68 ++++++++++- test/spec/modules/sevioBidAdapter_spec.js | 135 ++++++++++++++++++++++ 3 files changed, 209 insertions(+), 3 deletions(-) diff --git a/libraries/fpdUtils/pageInfo.js b/libraries/fpdUtils/pageInfo.js index 8e02134e070..3d23f317085 100644 --- a/libraries/fpdUtils/pageInfo.js +++ b/libraries/fpdUtils/pageInfo.js @@ -69,3 +69,12 @@ export function getReferrer(bidRequest = {}, bidderRequest = {}) { } return pageUrl; } + +/** + * get the document complexity + * @param document + * @returns {*|number} + */ +export function getDomComplexity(document) { + return document?.querySelectorAll('*')?.length ?? -1; +} diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index a5923ecc6a3..78ee46bf0f1 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -3,7 +3,11 @@ import { detectWalletsPresence} from "../libraries/cryptoUtils/wallets.js"; import { registerBidder } from "../src/adapters/bidderFactory.js"; import { BANNER, NATIVE } from "../src/mediaTypes.js"; import { config } from "../src/config.js"; +import {getDomComplexity, getPageDescription, getPageTitle} from "../libraries/fpdUtils/pageInfo.js"; import * as converter from '../libraries/ortbConverter/converter.js'; + +const PREBID_VERSION = '$prebid.version$'; +const ADAPTER_VERSION = '1.0'; const ORTB = converter.ortbConverter({ context: { ttl: 300 } }); @@ -17,6 +21,10 @@ const detectAdType = (bid) => ["native", "banner"].find((t) => bid.mediaTypes?.[t]) || "unknown" ).toUpperCase(); +const getReferrerInfo = (bidderRequest) => { + return bidderRequest?.refererInfo?.page ?? ''; +} + const parseNativeAd = function (bid) { try { const nativeAd = JSON.parse(bid.ad); @@ -123,14 +131,50 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const userSyncEnabled = config.getConfig("userSync.syncEnabled"); + // (!) that avoids top-level side effects (the thing that can stop registerBidder from running) + const computeTTFB = (w = (typeof window !== 'undefined' ? window : undefined)) => { + try { + const wt = (() => { try { return w?.top ?? w; } catch { return w; } })(); + const p = wt?.performance || wt?.webkitPerformance || wt?.msPerformance || wt?.mozPerformance; + if (!p) return ''; + + if (typeof p.getEntriesByType === 'function') { + const nav = p.getEntriesByType('navigation')?.[0]; + if (nav?.responseStart > 0 && nav?.requestStart > 0) { + return String(Math.round(nav.responseStart - nav.requestStart)); + } + } + + const t = p.timing; + if (t?.responseStart > 0 && t?.requestStart > 0) { + return String(t.responseStart - t.requestStart); + } + + return ''; + } catch { + return ''; + } + }; + + // simple caching + const getTTFBOnce = (() => { + let cached = false; + let done = false; + return () => { + if (done) return cached; + done = true; + cached = computeTTFB(); + return cached; + }; + })(); const ortbRequest = ORTB.toORTB({ bidderRequest, bidRequests }); if (bidRequests.length === 0) { return []; } - const gdpr = bidderRequest.gdprConsent; - const usp = bidderRequest.uspConsent; - const gpp = bidderRequest.gppConsent; + const gdpr = bidderRequest?.gdprConsent; + const usp = bidderRequest?.uspConsent; + const gpp = bidderRequest?.gppConsent; const hasWallet = detectWalletsPresence(); return bidRequests.map((bidRequest) => { @@ -139,6 +183,7 @@ export const spec = { const width = size[0]; const height = size[1]; const originalAssets = bidRequest.mediaTypes?.native?.ortb?.assets || []; + // convert icon to img type 1 const processedAssets = originalAssets.map(asset => { if (asset.icon) { @@ -186,6 +231,23 @@ export const spec = { wdb: hasWallet, externalRef: bidRequest.bidId, userSyncOption: userSyncEnabled === false ? "OFF" : "BIDDERS", + referer: getReferrerInfo(bidderRequest), + pageReferer: document.referrer, + pageTitle: getPageTitle().slice(0, 300), + pageDescription: getPageDescription().slice(0, 300), + domComplexity: getDomComplexity(document), + device: bidderRequest?.ortb2?.device || {}, + deviceWidth: screen.width, + deviceHeight: screen.height, + timeout: bidderRequest?.timeout, + viewportHeight: utils.getWinDimensions().visualViewport.height, + viewportWidth: utils.getWinDimensions().visualViewport.width, + timeToFirstByte: getTTFBOnce(), + ext: { + ...(bidderRequest?.ortb2?.ext || {}), + adapter_version: ADAPTER_VERSION, + prebid_version: PREBID_VERSION + } }; const wrapperOn = diff --git a/test/spec/modules/sevioBidAdapter_spec.js b/test/spec/modules/sevioBidAdapter_spec.js index 9e6050640c2..2f4d3bf337d 100644 --- a/test/spec/modules/sevioBidAdapter_spec.js +++ b/test/spec/modules/sevioBidAdapter_spec.js @@ -274,4 +274,139 @@ describe('sevioBidAdapter', function () { expect(requests[0].data.keywords).to.have.property('tokens'); expect(requests[0].data.keywords.tokens).to.deep.equal(['keyword1', 'keyword2']); }); + + // Minimal env shims some helpers rely on + Object.defineProperty(window, 'visualViewport', { + value: { width: 1200, height: 800 }, + configurable: true + }); + Object.defineProperty(window, 'screen', { + value: { width: 1920, height: 1080 }, + configurable: true + }); + + function mkBid(overrides) { + return Object.assign({ + bidId: 'bid-1', + bidder: 'sevio', + params: { zone: 'zone-123', referenceId: 'ref-abc', keywords: ['k1', 'k2'] }, + mediaTypes: { banner: { sizes: [[300, 250]] } }, + refererInfo: { page: 'https://example.com/page', referer: 'https://referrer.example' }, + userIdAsEids: [] + }, overrides || {}); + } + + const baseBidderRequest = { + timeout: 1200, + refererInfo: { page: 'https://example.com/page', referer: 'https://referrer.example' }, + gdprConsent: { consentString: 'TCF-STRING' }, + uspConsent: { uspString: '1NYN' }, + gppConsent: { consentString: 'GPP-STRING' }, + ortb2: { device: {}, ext: {} } + }; + + describe('Sevio adapter helper coverage via buildRequests (JS)', () => { + let stubs = []; + + afterEach(() => { + while (stubs.length) stubs.pop().restore(); + document.title = ''; + document.head.innerHTML = ''; + try { + Object.defineProperty(navigator, 'connection', { value: undefined, configurable: true }); + } catch (e) {} + }); + + it('getReferrerInfo → data.referer', () => { + const out = spec.buildRequests([mkBid()], baseBidderRequest); + expect(out).to.have.lengthOf(1); + expect(out[0].data.referer).to.equal('https://example.com/page'); + }); + + it('getPageTitle prefers top.title; falls back to og:title (top document)', () => { + window.top.document.title = 'Doc Title'; + let out = spec.buildRequests([mkBid()], baseBidderRequest); + expect(out[0].data.pageTitle).to.equal('Doc Title'); + + window.top.document.title = ''; + const meta = window.top.document.createElement('meta'); + meta.setAttribute('property', 'og:title'); + meta.setAttribute('content', 'OG Title'); + window.top.document.head.appendChild(meta); + + out = spec.buildRequests([mkBid()], baseBidderRequest); + expect(out[0].data.pageTitle).to.equal('OG Title'); + + meta.remove(); + }); + + it('getPageTitle cross-origin fallback (window.top throws) uses local document.*', function () { + document.title = 'Local Title'; + + // In jsdom, window.top === window; try to simulate cross-origin by throwing from getter. + let restored = false; + try { + const original = Object.getOwnPropertyDescriptor(window, 'top'); + Object.defineProperty(window, 'top', { + configurable: true, + get() { throw new Error('cross-origin'); } + }); + const out = spec.buildRequests([mkBid()], baseBidderRequest); + expect(out[0].data.pageTitle).to.equal('Local Title'); + Object.defineProperty(window, 'top', original); + restored = true; + } catch (e) { + // Environment didn’t allow redefining window.top; skip this case + this.skip(); + } finally { + if (!restored) { + try { Object.defineProperty(window, 'top', { value: window, configurable: true }); } catch (e) {} + } + } + }); + + it('computeTTFB via navigation entries (top.performance) and cached within call', () => { + const perfTop = window.top.performance; + + const original = perfTop.getEntriesByType; + Object.defineProperty(perfTop, 'getEntriesByType', { + configurable: true, writable: true, + value: (type) => (type === 'navigation' ? [{ responseStart: 152, requestStart: 100 }] : []) + }); + + const out = spec.buildRequests([mkBid({ bidId: 'A' }), mkBid({ bidId: 'B' })], baseBidderRequest); + expect(out).to.have.lengthOf(2); + expect(out[0].data.timeToFirstByte).to.equal('52'); + expect(out[1].data.timeToFirstByte).to.equal('52'); + + Object.defineProperty(perfTop, 'getEntriesByType', { configurable: true, writable: true, value: original }); + }); + + it('computeTTFB falls back to top.performance.timing when no navigation entries', () => { + const perfTop = window.top.performance; + const originalGetEntries = perfTop.getEntriesByType; + const originalTimingDesc = Object.getOwnPropertyDescriptor(perfTop, 'timing'); + + Object.defineProperty(perfTop, 'getEntriesByType', { + configurable: true, writable: true, value: () => [] + }); + + Object.defineProperty(perfTop, 'timing', { + configurable: true, + value: { responseStart: 250, requestStart: 200 } + }); + + const out = spec.buildRequests([mkBid()], baseBidderRequest); + expect(out[0].data.timeToFirstByte).to.equal('50'); + + Object.defineProperty(perfTop, 'getEntriesByType', { + configurable: true, writable: true, value: originalGetEntries + }); + if (originalTimingDesc) { + Object.defineProperty(perfTop, 'timing', originalTimingDesc); + } else { + Object.defineProperty(perfTop, 'timing', { configurable: true, value: undefined }); + } + }); + }); }); From fe330c5f35726f0b1082606f0bed2915373c15ed Mon Sep 17 00:00:00 2001 From: hieund-geniee <127374021+hieund-geniee@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:02:20 +0700 Subject: [PATCH 094/147] SSP Genie Bid Adapter: Change logic of specified currency bid-params do not refer to adServerCurrency. (#14061) * modify adUnit infomation * fix imuid module * feat(GenieeBidAdapter): Add support for GPID and pbadslot - Add support for GPID (Global Placement ID) from ortb2Imp.ext.gpid - Add fallback support for ortb2Imp.ext.data.pbadslot - Include gpid parameter in request when GPID exists - Add test cases to verify GPID, pbadslot, and priority behavior * Aladdin Bidder ID5 Compatible Adapter * add comment * modified test message * the import of buildExtuidQuery was missing * test: add test cases for id5id in extuid query * delete duplicate test * feat(GenieeBidAdapter): Add support for iframe-based cookie sync in BidAdapter * [CARPET-5190] Bid Adapter: Fix the default value of the ib parameter - change default value ib parameter * remove ib param * fix test/spec/modules/ssp_genieeBidAdapter_spec.js * Corrected cookie sync URL and added title to data * reset document.title after test * Modify document.title in sandbox.stub * CARPET-6134 Change logic of specified currency bidparams * format code * update assert in unit test * update code * update code * update reset config * Update assert to expect * Update logic --------- Co-authored-by: Murano Takamasa Co-authored-by: daikichiteranishi <49385718+daikichiteranishi@users.noreply.github.com> Co-authored-by: teranishi daikichi Co-authored-by: gn-daikichi <49385718+gn-daikichi@users.noreply.github.com> Co-authored-by: takumi-furukawa Co-authored-by: furukawaTakumi <45890154+furukawaTakumi@users.noreply.github.com> Co-authored-by: furukawaTakumi Co-authored-by: haruki-yamaguchi Co-authored-by: haruki yamaguchi <100411113+hrkhito@users.noreply.github.com> Co-authored-by: Thanh Tran Co-authored-by: thanhtran-geniee <135969265+thanhtran-geniee@users.noreply.github.com> --- modules/ssp_genieeBidAdapter.js | 19 +++-- .../spec/modules/ssp_genieeBidAdapter_spec.js | 70 +++++++++++-------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/modules/ssp_genieeBidAdapter.js b/modules/ssp_genieeBidAdapter.js index c768c511e9c..c4464f0f59a 100644 --- a/modules/ssp_genieeBidAdapter.js +++ b/modules/ssp_genieeBidAdapter.js @@ -342,11 +342,22 @@ export const spec = { */ isBidRequestValid: function (bidRequest) { if (!bidRequest.params.zoneId) return false; - const currencyType = config.getConfig('currency.adServerCurrency'); - if (typeof currencyType === 'string' && ALLOWED_CURRENCIES.indexOf(currencyType) === -1) { - utils.logError('Invalid currency type, we support only JPY and USD!'); - return false; + + if (bidRequest.params.hasOwnProperty('currency')) { + const bidCurrency = bidRequest.params.currency; + + if (!ALLOWED_CURRENCIES.includes(bidCurrency)) { + utils.logError(`[${BIDDER_CODE}] Currency "${bidCurrency}" in bid params is not supported. Supported are: ${ALLOWED_CURRENCIES.join(', ')}.`); + return false; + } + } else { + const adServerCurrency = config.getConfig('currency.adServerCurrency'); + if (typeof adServerCurrency === 'string' && !ALLOWED_CURRENCIES.includes(adServerCurrency)) { + utils.logError(`[${BIDDER_CODE}] adServerCurrency "${adServerCurrency}" is not supported. Supported are: ${ALLOWED_CURRENCIES.join(', ')}.`); + return false; + } } + return true; }, /** diff --git a/test/spec/modules/ssp_genieeBidAdapter_spec.js b/test/spec/modules/ssp_genieeBidAdapter_spec.js index 980d97c4c12..ab7e99ab5e5 100644 --- a/test/spec/modules/ssp_genieeBidAdapter_spec.js +++ b/test/spec/modules/ssp_genieeBidAdapter_spec.js @@ -82,42 +82,56 @@ describe('ssp_genieeBidAdapter', function () { afterEach(function () { sandbox.restore(); + config.resetConfig(); }); describe('isBidRequestValid', function () { - it('should return true when params.zoneId exists and params.currency does not exist', function () { - expect(spec.isBidRequestValid(BANNER_BID)).to.be.true; + it('should return false when params.zoneId does not exist', function () { + expect(spec.isBidRequestValid({ ...BANNER_BID, params: {} })).to.be.false; }); - it('should return true when params.zoneId and params.currency exist and params.currency is JPY or USD', function () { - config.setConfig({ currency: { adServerCurrency: 'JPY' } }); - expect( - spec.isBidRequestValid({ - ...BANNER_BID, - params: { ...BANNER_BID.params }, - }) - ).to.be.true; - config.setConfig({ currency: { adServerCurrency: 'USD' } }); - expect( - spec.isBidRequestValid({ - ...BANNER_BID, - params: { ...BANNER_BID.params }, - }) - ).to.be.true; - }); + describe('when params.currency is specified', function() { + it('should return true if currency is USD', function() { + const bid = { ...BANNER_BID, params: { ...BANNER_BID.params, currency: 'USD' } }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); - it('should return false when params.zoneId does not exist', function () { - expect(spec.isBidRequestValid({ ...BANNER_BID, params: {} })).to.be.false; + it('should return true if currency is JPY', function() { + const bid = { ...BANNER_BID, params: { ...BANNER_BID.params, currency: 'JPY' } }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false if currency is not supported (e.g., EUR)', function() { + const bid = { ...BANNER_BID, params: { ...BANNER_BID.params, currency: 'EUR' } }; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return true if currency is valid, ignoring adServerCurrency', function() { + config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + const bid = { ...BANNER_BID, params: { ...BANNER_BID.params, currency: 'USD' } }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); }); - it('should return false when params.zoneId and params.currency exist and params.currency is neither JPY nor USD', function () { - config.setConfig({ currency: { adServerCurrency: 'EUR' } }); - expect( - spec.isBidRequestValid({ - ...BANNER_BID, - params: { ...BANNER_BID.params }, - }) - ).to.be.false; + describe('when params.currency is NOT specified (fallback to adServerCurrency)', function() { + it('should return true if adServerCurrency is not set', function() { + expect(spec.isBidRequestValid(BANNER_BID)).to.be.true; + }); + + it('should return true if adServerCurrency is JPY', function() { + config.setConfig({ currency: { adServerCurrency: 'JPY' } }); + expect(spec.isBidRequestValid(BANNER_BID)).to.be.true; + }); + + it('should return true if adServerCurrency is USD', function() { + config.setConfig({ currency: { adServerCurrency: 'USD' } }); + expect(spec.isBidRequestValid(BANNER_BID)).to.be.true; + }); + + it('should return false if adServerCurrency is not supported (e.g., EUR)', function() { + config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + expect(spec.isBidRequestValid(BANNER_BID)).to.be.false; + }); }); }); From 7ce1e1b73f63145ffa11ebbde74fdab735d40d30 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Mon, 10 Nov 2025 13:10:54 +0100 Subject: [PATCH 095/147] Nexx360 Bid Adapter : typescript conversion & ybidder alias added (#13987) * typescript conversion & ybidder alias added * fixes * https fix --------- Co-authored-by: Gabriel Chicoye --- libraries/nexx360Utils/index.js | 155 ----------- libraries/nexx360Utils/index.ts | 246 ++++++++++++++++++ modules/adgridBidAdapter.js | 133 ---------- modules/adgridBidAdapter.ts | 102 ++++++++ ...x360BidAdapter.js => nexx360BidAdapter.ts} | 149 +++++------ test/spec/modules/adgridBidAdapter_spec.js | 19 +- test/spec/modules/nexx360BidAdapter_spec.js | 25 +- 7 files changed, 440 insertions(+), 389 deletions(-) delete mode 100644 libraries/nexx360Utils/index.js create mode 100644 libraries/nexx360Utils/index.ts delete mode 100644 modules/adgridBidAdapter.js create mode 100644 modules/adgridBidAdapter.ts rename modules/{nexx360BidAdapter.js => nexx360BidAdapter.ts} (55%) diff --git a/libraries/nexx360Utils/index.js b/libraries/nexx360Utils/index.js deleted file mode 100644 index b7423148204..00000000000 --- a/libraries/nexx360Utils/index.js +++ /dev/null @@ -1,155 +0,0 @@ -import { deepAccess, deepSetValue, logInfo } from '../../src/utils.js'; -import {Renderer} from '../../src/Renderer.js'; -import { getCurrencyFromBidderRequest } from '../ortb2Utils/currency.js'; -import { INSTREAM, OUTSTREAM } from '../../src/video.js'; -import { BANNER, NATIVE } from '../../src/mediaTypes.js'; - -const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -/** - * Register the user sync pixels which should be dropped after the auction. - * - /** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - * - */ - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ -export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (typeof serverResponses === 'object' && - serverResponses != null && - serverResponses.length > 0 && - serverResponses[0].hasOwnProperty('body') && - serverResponses[0].body.hasOwnProperty('ext') && - serverResponses[0].body.ext.hasOwnProperty('cookies') && - typeof serverResponses[0].body.ext.cookies === 'object') { - return serverResponses[0].body.ext.cookies.slice(0, 5); - } else { - return []; - } -}; - -function outstreamRender(response) { - response.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [response.width, response.height], - targetId: response.divId, - adResponse: response.vastXml, - rendererOptions: { - showBigPlayButton: false, - showProgressBar: 'bar', - showVolume: false, - allowFullscreen: true, - skippable: false, - content: response.vastXml - } - }); - }); -}; - -export function createRenderer(bid, url) { - const renderer = Renderer.install({ - id: bid.id, - url: url, - loaded: false, - adUnitCode: bid.ext.adUnitCode, - targetId: bid.ext.divId, - }); - renderer.setRender(outstreamRender); - return renderer; -}; - -export function enrichImp(imp, bidRequest) { - deepSetValue(imp, 'tagid', bidRequest.adUnitCode); - deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); - const divId = bidRequest.params.divId || bidRequest.adUnitCode; - deepSetValue(imp, 'ext.divId', divId); - if (imp.video) { - const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - deepSetValue(imp, 'video.ext.playerSize', playerSize); - deepSetValue(imp, 'video.ext.context', videoContext); - } - return imp; -} - -export function enrichRequest(request, amxId, bidderRequest, pageViewId, bidderVersion) { - if (amxId) { - deepSetValue(request, 'ext.localStorage.amxId', amxId); - if (!request.user) request.user = {}; - if (!request.user.ext) request.user.ext = {}; - if (!request.user.ext.eids) request.user.ext.eids = []; - request.user.ext.eids.push({ - source: 'amxdt.net', - uids: [{ - id: `${amxId}`, - atype: 1 - }] - }); - } - deepSetValue(request, 'ext.version', '$prebid.version$'); - deepSetValue(request, 'ext.source', 'prebid.js'); - deepSetValue(request, 'ext.pageViewId', pageViewId); - deepSetValue(request, 'ext.bidderVersion', bidderVersion); - deepSetValue(request, 'cur', [getCurrencyFromBidderRequest(bidderRequest) || 'USD']); - if (!request.user) request.user = {}; - return request; -}; - -export function createResponse(bid, respBody) { - const response = { - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - currency: respBody.cur, - netRevenue: true, - ttl: 120, - mediaType: [OUTSTREAM, INSTREAM].includes(bid.ext.mediaType) ? 'video' : bid.ext.mediaType, - meta: { - advertiserDomains: bid.adomain, - demandSource: bid.ext.ssp, - }, - }; - if (bid.dealid) response.dealid = bid.dealid; - - if (bid.ext.mediaType === BANNER) response.ad = bid.adm; - if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.adm; - - if (bid.ext.mediaType === OUTSTREAM) { - response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); - if (bid.ext.divId) response.divId = bid.ext.divId - }; - - if (bid.ext.mediaType === NATIVE) { - try { - response.native = { ortb: JSON.parse(bid.adm) } - } catch (e) {} - } - return response; -} - -/** - * Get the AMX ID - * @return { string | false } false if localstorageNotEnabled - */ -export function getAmxId(storage, bidderCode) { - if (!storage.localStorageIsEnabled()) { - logInfo(`localstorage not enabled for ${bidderCode}`); - return false; - } - const amxId = storage.getDataFromLocalStorage('__amuidpb'); - return amxId || false; -} diff --git a/libraries/nexx360Utils/index.ts b/libraries/nexx360Utils/index.ts new file mode 100644 index 00000000000..dec3ce47056 --- /dev/null +++ b/libraries/nexx360Utils/index.ts @@ -0,0 +1,246 @@ +import { deepAccess, deepSetValue, generateUUID, logInfo } from '../../src/utils.js'; +import {Renderer} from '../../src/Renderer.js'; +import { getCurrencyFromBidderRequest } from '../ortb2Utils/currency.js'; +import { INSTREAM, OUTSTREAM } from '../../src/video.js'; +import { BANNER, MediaType, NATIVE, VIDEO } from '../../src/mediaTypes.js'; +import { BidResponse, VideoBidResponse } from '../../src/bidfactory.js'; +import { StorageManager } from '../../src/storageManager.js'; +import { BidRequest, ORTBRequest, ORTBResponse } from '../../src/prebid.public.js'; +import { AdapterResponse, ServerResponse } from '../../src/adapters/bidderFactory.js'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +let sessionId:string | null = null; + +const getSessionId = ():string => { + if (!sessionId) { + sessionId = generateUUID(); + } + return sessionId; +} + +let lastPageUrl:string = ''; +let requestCounter:number = 0; + +const getRequestCount = ():number => { + if (lastPageUrl === window.location.pathname) { + return ++requestCounter; + } + lastPageUrl = window.location.pathname; + return 0; +} + +export const getLocalStorageFunctionGenerator = < + T extends Record +>( + storage: StorageManager, + bidderCode: string, + storageKey: string, + jsonKey: keyof T + ): (() => T | null) => { + return () => { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for ${bidderCode}`); + return null; + } + + const output = storage.getDataFromLocalStorage(storageKey); + if (output === null) { + const storageElement: T = { [jsonKey]: generateUUID() } as T; + storage.setDataInLocalStorage(storageKey, JSON.stringify(storageElement)); + return storageElement; + } + try { + return JSON.parse(output) as T; + } catch (e) { + logInfo(`failed to parse localstorage for ${bidderCode}:`, e); + return null; + } + }; +}; + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (typeof serverResponses === 'object' && + serverResponses != null && + serverResponses.length > 0 && + serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('ext') && + serverResponses[0].body.ext.hasOwnProperty('cookies') && + typeof serverResponses[0].body.ext.cookies === 'object') { + return serverResponses[0].body.ext.cookies.slice(0, 5); + } else { + return []; + } +}; + +const createOustreamRendererFunction = ( + divId: string, + width: number, + height: number +) => (bidResponse: VideoBidResponse) => { + bidResponse.renderer.push(() => { + (window as any).ANOutstreamVideo.renderAd({ + sizes: [width, height], + targetId: divId, + adResponse: bidResponse.vastXml, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bidResponse.vastXml + } + }); + }); +}; + +export type CreateRenderPayload = { + requestId: string, + vastXml: string, + divId: string, + width: number, + height: number +} + +export const createRenderer = ( + { requestId, vastXml, divId, width, height }: CreateRenderPayload +): Renderer | undefined => { + if (!vastXml) { + logInfo('No VAST in bidResponse'); + return; + } + const installPayload = { + id: requestId, + url: OUTSTREAM_RENDERER_URL, + loaded: false, + adUnitCode: divId, + targetId: divId, + }; + const renderer = Renderer.install(installPayload); + renderer.setRender(createOustreamRendererFunction(divId, width, height)); + return renderer; +}; + +export const enrichImp = (imp, bidRequest:BidRequest) => { + deepSetValue(imp, 'tagid', bidRequest.adUnitCode); + deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); + const divId = bidRequest.params.divId || bidRequest.adUnitCode; + deepSetValue(imp, 'ext.divId', divId); + if (imp.video) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.playerSize', playerSize); + deepSetValue(imp, 'video.ext.context', videoContext); + } + return imp; +} + +export const enrichRequest = ( + request: ORTBRequest, + amxId: string | null, + pageViewId: string, + bidderVersion: string):ORTBRequest => { + if (amxId) { + deepSetValue(request, 'ext.localStorage.amxId', amxId); + if (!request.user) request.user = {}; + if (!request.user.ext) request.user.ext = {}; + if (!request.user.ext.eids) request.user.ext.eids = []; + (request.user.ext.eids as any).push({ + source: 'amxdt.net', + uids: [{ + id: `${amxId}`, + atype: 1 + }] + }); + } + deepSetValue(request, 'ext.version', '$prebid.version$'); + deepSetValue(request, 'ext.source', 'prebid.js'); + deepSetValue(request, 'ext.pageViewId', pageViewId); + deepSetValue(request, 'ext.bidderVersion', bidderVersion); + deepSetValue(request, 'ext.sessionId', getSessionId()); + deepSetValue(request, 'ext.requestCounter', getRequestCount()); + deepSetValue(request, 'cur', [getCurrencyFromBidderRequest(request) || 'USD']); + if (!request.user) request.user = {}; + return request; +}; + +export function createResponse(bid:any, ortbResponse:any): BidResponse { + let mediaType: MediaType = BANNER; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType as string)) mediaType = VIDEO; + if (bid.ext.mediaType === NATIVE) mediaType = NATIVE; + const response:any = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: ortbResponse.cur, + netRevenue: true, + ttl: 120, + mediaType, + meta: { + advertiserDomains: bid.adomain, + demandSource: bid.ext.ssp, + }, + }; + if (bid.dealid) response.dealid = bid.dealid; + + if (bid.ext.mediaType === BANNER) response.ad = bid.adm; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType as string)) response.vastXml = bid.adm; + if (bid.ext.mediaType === OUTSTREAM && (bid.ext.divId || bid.ext.adUnitCode)) { + const renderer = createRenderer({ + requestId: response.requestId, + vastXml: response.vastXml, + divId: bid.ext.divId || bid.ext.adUnitCode, + width: response.width, + height: response.height + }); + if (renderer) { + response.renderer = renderer; + response.divId = bid.ext.divId; + } else { + logInfo('Could not create renderer for outstream bid'); + } + }; + + if (bid.ext.mediaType === NATIVE) { + try { + response.native = { ortb: JSON.parse(bid.adm) } + } catch (e) {} + } + return response as BidResponse; +} + +export const interpretResponse = (serverResponse: ServerResponse): AdapterResponse => { + if (!serverResponse.body) return []; + const respBody = serverResponse.body as ORTBResponse; + if (!respBody.seatbid || respBody.seatbid.length === 0) return []; + + const responses: BidResponse[] = []; + for (let i = 0; i < respBody.seatbid.length; i++) { + const seatbid = respBody.seatbid[i]; + for (let j = 0; j < seatbid.bid.length; j++) { + const bid = seatbid.bid[j]; + const response:BidResponse = createResponse(bid, respBody); + responses.push(response); + } + } + return responses; +} + +/** + * Get the AMX ID + * @return { string | false } false if localstorageNotEnabled + */ +export const getAmxId = ( + storage: StorageManager, + bidderCode: string +): string | null => { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for ${bidderCode}`); + return null; + } + const amxId = storage.getDataFromLocalStorage('__amuidpb'); + return amxId || null; +} diff --git a/modules/adgridBidAdapter.js b/modules/adgridBidAdapter.js deleted file mode 100644 index d1cccf21c52..00000000000 --- a/modules/adgridBidAdapter.js +++ /dev/null @@ -1,133 +0,0 @@ -import { deepSetValue, generateUUID, logInfo } from '../src/utils.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { createResponse, enrichImp, enrichRequest, getAmxId, getUserSyncs } from '../libraries/nexx360Utils/index.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - */ - -const BIDDER_CODE = 'adgrid'; -const REQUEST_URL = 'https://fast.nexx360.io/adgrid'; -const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '2.0'; -const ADGRID_KEY = 'adgrid'; - -const ALIASES = []; - -// Define the storage manager for the Adgrid bidder -export const STORAGE = getStorageManager({ - bidderCode: BIDDER_CODE, -}); - -/** - * Get the agdridId from local storage - * @return {object | false } false if localstorageNotEnabled - */ -export function getLocalStorage() { - if (!STORAGE.localStorageIsEnabled()) { - logInfo(`localstorage not enabled for Adgrid`); - return false; - } - const output = STORAGE.getDataFromLocalStorage(ADGRID_KEY); - if (output === null) { - const adgridStorage = { adgridId: generateUUID() }; - STORAGE.setDataInLocalStorage(ADGRID_KEY, JSON.stringify(adgridStorage)); - return adgridStorage; - } - try { - return JSON.parse(output); - } catch (e) { - return false; - } -} - -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, context) { - let imp = buildImp(bidRequest, context); - imp = enrichImp(imp, bidRequest); - if (bidRequest.params.domainId) deepSetValue(imp, 'ext.adgrid.domainId', bidRequest.params.domainId); - if (bidRequest.params.placement) deepSetValue(imp, 'ext.adgrid.placement', bidRequest.params.placement); - return imp; - }, - request(buildRequest, imps, bidderRequest, context) { - let request = buildRequest(imps, bidderRequest, context); - const amxId = getAmxId(STORAGE, BIDDER_CODE); - request = enrichRequest(request, amxId, bidderRequest, PAGE_VIEW_ID, BIDDER_VERSION); - return request; - }, -}); - -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { - if (!bid || !bid.params) return false; - if (typeof bid.params.domainId !== 'number') return false; - if (typeof bid.params.placement !== 'string') return false; - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(bidRequests, bidderRequest) { - const data = converter.toORTB({ bidRequests, bidderRequest }) - return { - method: 'POST', - url: REQUEST_URL, - data, - } -} - -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(serverResponse) { - const respBody = serverResponse.body; - if (!respBody || !Array.isArray(respBody.seatbid)) { - return []; - } - - const responses = []; - for (let i = 0; i < respBody.seatbid.length; i++) { - const seatbid = respBody.seatbid[i]; - for (let j = 0; j < seatbid.bid.length; j++) { - const bid = seatbid.bid[j]; - const response = createResponse(bid, respBody); - responses.push(response); - } - } - return responses; -} - -export const spec = { - code: BIDDER_CODE, - aliases: ALIASES, - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, -}; - -registerBidder(spec); diff --git a/modules/adgridBidAdapter.ts b/modules/adgridBidAdapter.ts new file mode 100644 index 00000000000..e5ba2c8b672 --- /dev/null +++ b/modules/adgridBidAdapter.ts @@ -0,0 +1,102 @@ +import { deepSetValue, generateUUID } from '../src/utils.js'; +import { getStorageManager, StorageManager } from '../src/storageManager.js'; +import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { ORTBRequest } from '../src/prebid.public.js'; + +const BIDDER_CODE = 'adgrid'; +const REQUEST_URL = 'https://fast.nexx360.io/adgrid'; +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '2.0'; +const ADGRID_KEY = 'adgrid'; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type AdgridBidParams = RequireAtLeastOne<{ + domainId?: string; + placement?: string; + allBids?: boolean; + customId?: string; +}, "domainId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: AdgridBidParams; + } +} + +const ALIASES = []; + +// Define the storage manager for the Adgrid bidder +export const STORAGE: StorageManager = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +export const getAdgridLocalStorage = getLocalStorageFunctionGenerator<{ adgridId: string }>( + STORAGE, + BIDDER_CODE, + ADGRID_KEY, + 'adgridId' +); + +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, context) { + let imp = buildImp(bidRequest, context); + imp = enrichImp(imp, bidRequest); + const params = bidRequest.params as AdgridBidParams; + if (params.domainId) deepSetValue(imp, 'ext.adgrid.domainId', params.domainId); + if (params.placement) deepSetValue(imp, 'ext.adgrid.placement', params.placement); + if (params.allBids) deepSetValue(imp, 'ext.adgrid.allBids', params.allBids); + if (params.customId) deepSetValue(imp, 'ext.adgrid.customId', params.customId); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + let request = 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 || !bid.params) return false; + if (typeof bid.params.domainId !== 'number') return false; + if (typeof bid.params.placement !== 'string') 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, + } + return adapterRequest; +} + +export const spec:BidderSpec = { + code: BIDDER_CODE, + aliases: ALIASES, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.ts similarity index 55% rename from modules/nexx360BidAdapter.js rename to modules/nexx360BidAdapter.ts index c89238b9242..393bea2dbab 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.ts @@ -1,29 +1,48 @@ -import { deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js'; +import { deepSetValue, generateUUID, logError } from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {AdapterRequest, BidderSpec, registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' -import { createResponse, enrichImp, enrichRequest, getAmxId, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - */ +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '6.3'; +const BIDDER_VERSION = '7.0'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; +const DEFAULT_GZIP_ENABLED = false; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type Nexx360BidParams = RequireAtLeastOne<{ + tagId?: string; + placement?: string; + videoTagId?: string; + nativeTagId?: string; + adUnitPath?: string; + adUnitName?: string; + divId?: string; + allBids?: boolean; + customId?: string; +}, "tagId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: Nexx360BidParams; + } +} + const ALIASES = [ { code: 'revenuemaker' }, { code: 'first-id', gvlid: 1178 }, @@ -41,34 +60,26 @@ const ALIASES = [ { code: 'glomexbidder', gvlid: 967 }, { code: 'revnew', gvlid: 1468 }, { code: 'pubxai', gvlid: 1485 }, + { code: 'ybidder', gvlid: 1253 }, ]; export const STORAGE = getStorageManager({ bidderCode: BIDDER_CODE, }); -/** - * Get the NexxId - * @param - * @return {object | false } false if localstorageNotEnabled - */ - -export function getNexx360LocalStorage() { - if (!STORAGE.localStorageIsEnabled()) { - logInfo(`localstorage not enabled for Nexx360`); - return false; - } - const output = STORAGE.getDataFromLocalStorage(NEXXID_KEY); - if (output === null) { - const nexx360Storage = { nexx360Id: generateUUID() }; - STORAGE.setDataInLocalStorage(NEXXID_KEY, JSON.stringify(nexx360Storage)); - return nexx360Storage; - } - try { - return JSON.parse(output) - } catch (e) { - return false; +export const getNexx360LocalStorage = getLocalStorageFunctionGenerator<{ nexx360Id: string }>( + STORAGE, + BIDDER_CODE, + NEXXID_KEY, + 'nexx360Id' +); + +export const getGzipSetting = (): boolean => { + const getBidderConfig = config.getBidderConfig(); + if (getBidderConfig.nexx360?.gzipEnabled === 'true') { + return getBidderConfig.nexx360?.gzipEnabled === 'true'; } + return DEFAULT_GZIP_ENABLED; } const converter = ortbConverter({ @@ -77,10 +88,10 @@ const converter = ortbConverter({ ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) }, imp(buildImp, bidRequest, context) { - let imp = buildImp(bidRequest, context); + let imp:ORTBImp = buildImp(bidRequest, context); imp = enrichImp(imp, bidRequest); const divId = bidRequest.params.divId || bidRequest.adUnitCode; - const slotEl = document.getElementById(divId); + const slotEl:HTMLElement | null = typeof divId === 'string' ? document.getElementById(divId) : null; if (slotEl) { const { width, height } = getBoundingClientRect(slotEl); deepSetValue(imp, 'ext.dimensions.slotW', width); @@ -94,23 +105,19 @@ const converter = ortbConverter({ if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); if (bidRequest.params.allBids) deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); + if (bidRequest.params.nativeTagId) deepSetValue(imp, 'ext.nexx360.nativeTagId', bidRequest.params.nativeTagId); + if (bidRequest.params.customId) deepSetValue(imp, 'ext.nexx360.customId', bidRequest.params.customId); return imp; }, request(buildRequest, imps, bidderRequest, context) { - let request = buildRequest(imps, bidderRequest, context); + let request:ORTBRequest = buildRequest(imps, bidderRequest, context); const amxId = getAmxId(STORAGE, BIDDER_CODE); - request = enrichRequest(request, amxId, bidderRequest, PAGE_VIEW_ID, BIDDER_VERSION); + request = enrichRequest(request, amxId, PAGE_VIEW_ID, BIDDER_VERSION); return request; }, }); -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { +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; @@ -134,51 +141,23 @@ function isBidRequestValid(bid) { return true; }; -/** - * Make a server request from the list of BidRequests. - * - * @return ServerRequest Info describing the request to the server. - */ - -function buildRequests(bidRequests, bidderRequest) { - const data = converter.toORTB({bidRequests, bidderRequest}) - return { +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; } -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - -function interpretResponse(serverResponse) { - const respBody = serverResponse.body; - if (!respBody || !Array.isArray(respBody.seatbid)) { - return []; - } - - const { bidderSettings } = getGlobal(); - const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; - - const responses = []; - for (let i = 0; i < respBody.seatbid.length; i++) { - const seatbid = respBody.seatbid[i]; - for (let j = 0; j < seatbid.bid.length; j++) { - const bid = seatbid.bid[j]; - const response = createResponse(bid, respBody); - if (allowAlternateBidderCodes) response.bidderCode = `n360_${bid.ext.ssp}`; - responses.push(response); - } - } - return responses; -} - -export const spec = { +export const spec:BidderSpec = { code: BIDDER_CODE, gvlid: GVLID, aliases: ALIASES, diff --git a/test/spec/modules/adgridBidAdapter_spec.js b/test/spec/modules/adgridBidAdapter_spec.js index 5b6d658e1ca..3dd5d1985f5 100644 --- a/test/spec/modules/adgridBidAdapter_spec.js +++ b/test/spec/modules/adgridBidAdapter_spec.js @@ -1,9 +1,8 @@ import { expect } from 'chai'; import { - spec, STORAGE, getLocalStorage, + spec, STORAGE, getAdgridLocalStorage, } from 'modules/adgridBidAdapter.js'; import sinon from 'sinon'; -import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; const sandbox = sinon.createSandbox(); describe('adgrid bid adapter tests', () => { @@ -74,8 +73,8 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => false); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); - expect(output).to.be.eql(false); + const output = getAdgridLocalStorage(); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -89,7 +88,7 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => null); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); + const output = getAdgridLocalStorage(); expect(typeof output.adgridId).to.be.eql('string'); }); after(() => { @@ -104,8 +103,8 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"adgridId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); - expect(output).to.be.eql(false); + const output = getAdgridLocalStorage(); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -119,7 +118,7 @@ describe('adgrid bid adapter tests', () => { sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake((key) => '{"adgridId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); }); it('We test if we get the adgridId', () => { - const output = getLocalStorage(); + const output = getAdgridLocalStorage(); expect(output.adgridId).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); }); after(() => { @@ -299,6 +298,8 @@ describe('adgrid bid adapter tests', () => { source: 'prebid.js', pageViewId: requestContent.ext.pageViewId, bidderVersion: '2.0', + requestCounter: 0, + sessionId: requestContent.ext.sessionId, }, cur: [ 'USD', @@ -514,6 +515,7 @@ describe('adgrid bid adapter tests', () => { mediaType: 'outstream', ssp: 'test', adUnitCode: 'div-1', + divId: 'div-1', }, }, ], @@ -536,6 +538,7 @@ describe('adgrid bid adapter tests', () => { currency: 'USD', netRevenue: true, ttl: 120, + divId: 'div-1', mediaType: 'video', meta: { advertiserDomains: ['adgrid.com'], demandSource: 'test' }, vastXml: 'vast', diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 7756e96bd99..a5e8ab03d1a 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { - spec, STORAGE, getNexx360LocalStorage, + spec, STORAGE, getNexx360LocalStorage, getGzipSetting, } from 'modules/nexx360BidAdapter.js'; import sinon from 'sinon'; import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; @@ -33,6 +33,11 @@ describe('Nexx360 bid adapter tests', () => { }, }; + it('We test getGzipSettings', () => { + const output = getGzipSetting(); + expect(output).to.be.a('boolean'); + }); + describe('isBidRequestValid()', () => { let bannerBid; beforeEach(() => { @@ -95,12 +100,12 @@ describe('Nexx360 bid adapter tests', () => { }); it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); - expect(output).to.be.eql(false); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() }); - }) + }); describe('getNexx360LocalStorage enabled but nothing', () => { before(() => { @@ -115,7 +120,7 @@ describe('Nexx360 bid adapter tests', () => { after(() => { sandbox.restore() }); - }) + }); describe('getNexx360LocalStorage enabled but wrong payload', () => { before(() => { @@ -125,7 +130,7 @@ describe('Nexx360 bid adapter tests', () => { }); it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); - expect(output).to.be.eql(false); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -155,7 +160,7 @@ describe('Nexx360 bid adapter tests', () => { }); it('We test if we get the amxId', () => { const output = getAmxId(STORAGE, 'nexx360'); - expect(output).to.be.eql(false); + expect(output).to.be.eql(null); }); after(() => { sandbox.restore() @@ -335,8 +340,10 @@ describe('Nexx360 bid adapter tests', () => { version: requestContent.ext.version, source: 'prebid.js', pageViewId: requestContent.ext.pageViewId, - bidderVersion: '6.3', - localStorage: { amxId: 'abcdef'} + bidderVersion: '7.0', + localStorage: { amxId: 'abcdef'}, + sessionId: requestContent.ext.sessionId, + requestCounter: 0, }, cur: [ 'USD', @@ -564,6 +571,7 @@ describe('Nexx360 bid adapter tests', () => { mediaType: 'outstream', ssp: 'appnexus', adUnitCode: 'div-1', + divId: 'div-1', }, }, ], @@ -585,6 +593,7 @@ describe('Nexx360 bid adapter tests', () => { creativeId: '97517771', currency: 'USD', netRevenue: true, + divId: 'div-1', ttl: 120, mediaType: 'video', meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, From 99c6ec3def28d545c0431c0301a9ecb5aff9efd4 Mon Sep 17 00:00:00 2001 From: Zach Bowman Date: Mon, 10 Nov 2025 07:14:05 -0500 Subject: [PATCH 096/147] ConnectID Adapter: fix storage type configuration not being respected (#14018) * fix: Update ConnectID Adapter to respect storage type configuration. * refactor: Improve ConnectID storage type handling with constants and documentation. --- modules/connectIdSystem.js | 49 ++++++-- test/spec/modules/connectIdSystem_spec.js | 134 +++++++++++++++++++++- 2 files changed, 168 insertions(+), 15 deletions(-) diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 01b7e91961a..006cb06d005 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -9,7 +9,7 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; -import {getStorageManager} from '../src/storageManager.js'; +import {getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE} from '../src/storageManager.js'; import {formatQS, isNumber, isPlainObject, logError, parseUrl} from '../src/utils.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; @@ -45,15 +45,23 @@ const O_AND_O_DOMAINS = [ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** + * Stores the ConnectID object in browser storage according to storage configuration * @function - * @param {Object} obj + * @param {Object} obj - The ID object to store + * @param {Object} [storageConfig={}] - Storage configuration + * @param {string} [storageConfig.type] - Storage type: 'cookie', 'html5', or 'cookie&html5' */ -function storeObject(obj) { +function storeObject(obj, storageConfig = {}) { const expires = Date.now() + STORAGE_DURATION; - if (storage.cookiesAreEnabled()) { + const storageType = storageConfig.type || ''; + + const useCookie = !storageType || storageType.includes(STORAGE_TYPE_COOKIES); + const useLocalStorage = !storageType || storageType.includes(STORAGE_TYPE_LOCALSTORAGE); + + if (useCookie && storage.cookiesAreEnabled()) { setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname()); } - if (storage.localStorageIsEnabled()) { + if (useLocalStorage && storage.localStorageIsEnabled()) { storage.setDataInLocalStorage(MODULE_NAME, JSON.stringify(obj)); } } @@ -110,8 +118,17 @@ function getIdFromLocalStorage() { return null; } -function syncLocalStorageToCookie() { - if (!storage.cookiesAreEnabled()) { +/** + * Syncs ID from localStorage to cookie if storage configuration allows + * @function + * @param {Object} [storageConfig={}] - Storage configuration + * @param {string} [storageConfig.type] - Storage type: 'cookie', 'html5', or 'cookie&html5' + */ +function syncLocalStorageToCookie(storageConfig = {}) { + const storageType = storageConfig.type || ''; + const useCookie = !storageType || storageType.includes(STORAGE_TYPE_COOKIES); + + if (!useCookie || !storage.cookiesAreEnabled()) { return; } const value = getIdFromLocalStorage(); @@ -129,12 +146,19 @@ function isStale(storedIdData) { return false; } -function getStoredId() { +/** + * Retrieves stored ConnectID from cookie or localStorage + * @function + * @param {Object} [storageConfig={}] - Storage configuration + * @param {string} [storageConfig.type] - Storage type: 'cookie', 'html5', or 'cookie&html5' + * @returns {Object|null} The stored ID object or null if not found + */ +function getStoredId(storageConfig = {}) { let storedId = getIdFromCookie(); if (!storedId) { storedId = getIdFromLocalStorage(); if (storedId && !isStale(storedId)) { - syncLocalStorageToCookie(); + syncLocalStorageToCookie(storageConfig); } } return storedId; @@ -191,13 +215,14 @@ export const connectIdSubmodule = { return; } const params = config.params || {}; + const storageConfig = config.storage || {}; if (!params || (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { logError(`${MODULE_NAME} module: configuration requires the 'pixelId'.`); return; } - const storedId = getStoredId(); + const storedId = getStoredId(storageConfig); let shouldResync = isStale(storedId); @@ -213,7 +238,7 @@ export const connectIdSubmodule = { } if (!shouldResync) { storedId.lastUsed = Date.now(); - storeObject(storedId); + storeObject(storedId, storageConfig); return {id: storedId}; } } @@ -274,7 +299,7 @@ export const connectIdSubmodule = { } responseObj.ttl = validTTLMiliseconds; } - storeObject(responseObj); + storeObject(responseObj, storageConfig); } else { logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`); } diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index b65096068fc..48ef3a30fe3 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -77,10 +77,14 @@ describe('Yahoo ConnectID Submodule', () => { removeLocalStorageDataStub.restore(); }); - function invokeGetIdAPI(configParams, consentData) { - const result = connectIdSubmodule.getId({ + function invokeGetIdAPI(configParams, consentData, storageConfig) { + const config = { params: configParams - }, consentData); + }; + if (storageConfig) { + config.storage = storageConfig; + } + const result = connectIdSubmodule.getId(config, consentData); if (typeof result === 'object' && result.callback) { result.callback(sinon.stub()); } @@ -803,6 +807,130 @@ describe('Yahoo ConnectID Submodule', () => { expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData)); }); + + it('stores the result in localStorage only when storage type is html5', () => { + getAjaxFnStub.restore(); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + const upsResponse = {connectid: 'html5only'}; + const expectedStoredData = { + connectid: 'html5only', + puid: PUBLISHER_USER_ID, + lastSynced: 0, + lastUsed: 0 + }; + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData, {type: 'html5'}); + const request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + dateNowStub.restore(); + + expect(setCookieStub.called).to.be.false; + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData)); + }); + + it('stores the result in cookie only when storage type is cookie', () => { + getAjaxFnStub.restore(); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + const upsResponse = {connectid: 'cookieonly'}; + const expectedStoredData = { + connectid: 'cookieonly', + puid: PUBLISHER_USER_ID, + lastSynced: 0, + lastUsed: 0 + }; + const expiryDelta = new Date(60 * 60 * 24 * 365 * 1000); + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData, {type: 'cookie'}); + const request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + dateNowStub.restore(); + + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(expectedStoredData)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + expect(setLocalStorageStub.called).to.be.false; + }); + + it('does not sync localStorage to cookie when storage type is html5', () => { + const localStorageData = {connectId: 'foobarbaz'}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData, {type: 'html5'}); + + expect(setCookieStub.called).to.be.false; + }); + + it('updates existing ID with html5 storage type without writing cookie', () => { + const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13); + const cookieData = {connectId: 'foobar', he: HASHED_EMAIL, lastSynced: last13Days}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(20); + const newCookieData = Object.assign({}, cookieData, {lastUsed: 20}) + const result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData, {type: 'html5'}); + dateNowStub.restore(); + + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(setCookieStub.called).to.be.false; + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.equal(JSON.stringify(newCookieData)); + }); + + it('stores the result in both storages when storage type is cookie&html5', () => { + getAjaxFnStub.restore(); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + const upsResponse = {connectid: 'both'}; + const expectedStoredData = { + connectid: 'both', + puid: PUBLISHER_USER_ID, + lastSynced: 0, + lastUsed: 0 + }; + const expiryDelta = new Date(60 * 60 * 24 * 365 * 1000); + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData, {type: 'cookie&html5'}); + const request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + dateNowStub.restore(); + + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(expectedStoredData)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData)); + }); }); }); describe('userHasOptedOut()', () => { From 3b517311c626c10fdf172b68aa30ba88539d3a48 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 12 Nov 2025 14:56:43 -0500 Subject: [PATCH 097/147] Vidazoo utils: fix screen resolution detection (#14122) * Fix Vidazoo utils screen resolution detection * remove duplication --------- Co-authored-by: Demetrio Girardi --- libraries/vidazooUtils/bidderUtils.js | 14 ++++++++++++-- test/spec/modules/shinezRtbBidAdapter_spec.js | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 08432936858..fa2cea1b6fa 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -6,7 +6,8 @@ import { parseSizesInput, parseUrl, triggerPixel, - uniques + uniques, + getWinDimensions } from '../../src/utils.js'; import {chunk} from '../chunk/chunk.js'; import {CURRENCY, DEAL_ID_EXPIRY, SESSION_ID_KEY, TTL_SECONDS, UNIQUE_DEAL_ID_EXPIRY} from './constants.js'; @@ -280,7 +281,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder uniqueDealId: uniqueDealId, bidderVersion: bidderVersion, prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, + res: getScreenResolution(), schain: schain, mediaTypes: mediaTypes, isStorageAllowed: isStorageAllowed, @@ -374,6 +375,15 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder return data; } +function getScreenResolution() { + const dimensions = getWinDimensions(); + const width = dimensions?.screen?.width; + const height = dimensions?.screen?.height; + if (width != null && height != null) { + return `${width}x${height}` + } +} + export function createInterpretResponseFn(bidderCode, allowSingleRequest) { return function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 443999989da..2dd33d7ef5b 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -14,7 +14,7 @@ import { tryParseJSON, getUniqueDealId, } from '../../../libraries/vidazooUtils/bidderUtils.js'; -import {parseUrl, deepClone} from 'src/utils.js'; +import {parseUrl, deepClone, getWinDimensions} from 'src/utils.js'; import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; @@ -428,7 +428,7 @@ describe('ShinezRtbBidAdapter', function () { bidderVersion: adapter.version, prebidVersion: version, schain: BID.schain, - res: `${window.top.screen.width}x${window.top.screen.height}`, + res: `${getWinDimensions().screen.width}x${getWinDimensions().screen.height}`, mediaTypes: [BANNER], gpid: '0123456789', uqs: getTopWindowQueryParams(), From f78328cde2e8a46e8ea3e8936a3860805d3fc6bd Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 12 Nov 2025 15:10:12 -0500 Subject: [PATCH 098/147] CI: run release drafter on legacy branches (#14124) * CI: run release drafter on legacy branches * Remove unnecessary blank line in workflow file --------- Co-authored-by: Patrick McCann --- .github/workflows/release-drafter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index a14e12664b6..53d458a3948 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -5,6 +5,7 @@ on: # branches to consider in the event; optional, defaults to all branches: - master + - '*.x-legacy' permissions: contents: read From a2f156fd97ec2f1476e29ebdd47473b3c16de4ec Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Wed, 12 Nov 2025 23:19:49 +0200 Subject: [PATCH 099/147] Attekmi Bid Adapter : add MarlinAds alias (#14094) * Attekmi: add MarlinAds alias * pid fix --------- Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 853c0d1a29a..e779fd8d43d 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -25,6 +25,7 @@ const ALIASES = { 'addigi': {area: '1', pid: '425'}, 'jambojar': {area: '1', pid: '426'}, 'anzu': {area: '1', pid: '445'}, + 'marlinads': {area: '1', pid: '397'}, }; const BASE_URLS = { @@ -40,6 +41,7 @@ const BASE_URLS = { 'jambojar': 'https://jambojar-prebid.attekmi.co/pbjs', 'jambojar-apac': 'https://jambojar-apac-prebid.attekmi.co/pbjs', 'anzu': 'https://anzu-prebid.attekmi.co/pbjs', + 'marlinads': 'https://marlinads-prebid.attekmi.co/pbjs', }; const adapterState = {}; From e9c38e44574d5c8baf6b44c4e2120c01dbf93e5b Mon Sep 17 00:00:00 2001 From: Siminko Vlad <85431371+siminkovladyslav@users.noreply.github.com> Date: Wed, 12 Nov 2025 22:20:44 +0100 Subject: [PATCH 100/147] OMS Bid Adapter: add banner media type check in buildRequests (#14117) --- modules/omsBidAdapter.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 289a763a8ac..68d93a123f7 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -51,18 +51,21 @@ function buildRequests(bidReqs, bidderRequest) { const imp = { id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded, - } - }, ext: { ...gpidData }, tagid: String(bid.adUnitCode) }; + if (bid?.mediaTypes?.banner) { + imp.banner = { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded, + } + } + } + if (bid?.mediaTypes?.video) { imp.video = { ...bid.mediaTypes.video, From b61cbfbefdb56fbb08d6b412069b663f389e11f6 Mon Sep 17 00:00:00 2001 From: yaiza-tappx Date: Wed, 12 Nov 2025 22:25:35 +0100 Subject: [PATCH 101/147] Tappx Adapter Fix: obtain Adomain from response and add test (#14113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feature: added obtention of gpid, divid and other information and adding tests * Fix: obtain Adomain from response and add test --------- Co-authored-by: Jordi Arnau Co-authored-by: jordi-tappx Co-authored-by: Yaiza Fernández --- modules/tappxBidAdapter.js | 5 +++-- test/spec/modules/tappxBidAdapter_spec.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 10b28c3c7ea..6ca3adb4d54 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -16,7 +16,7 @@ const BIDDER_CODE = 'tappx'; const GVLID_CODE = 628; const TTL = 360; const CUR = 'USD'; -const TAPPX_BIDDER_VERSION = '0.1.4'; +const TAPPX_BIDDER_VERSION = '0.1.5'; const TYPE_CNN = 'prebidjs'; const LOG_PREFIX = '[TAPPX]: '; const VIDEO_SUPPORT = ['instream', 'outstream']; @@ -209,6 +209,7 @@ function interpretBid(serverBid, request) { if (typeof serverBid.lurl !== 'undefined') { bidReturned.lurl = serverBid.lurl } if (typeof serverBid.nurl !== 'undefined') { bidReturned.nurl = serverBid.nurl } if (typeof serverBid.burl !== 'undefined') { bidReturned.burl = serverBid.burl } + if (typeof serverBid.adomain !== 'undefined') { bidReturned.adomain = serverBid.adomain } if (typeof request.bids?.mediaTypes !== 'undefined' && typeof request.bids?.mediaTypes.video !== 'undefined') { bidReturned.vastXml = serverBid.adm; @@ -231,7 +232,7 @@ function interpretBid(serverBid, request) { } if (typeof bidReturned.adomain !== 'undefined' || bidReturned.adomain !== null) { - bidReturned.meta = { advertiserDomains: request.bids?.adomain }; + bidReturned.meta = { advertiserDomains: bidReturned.adomain }; } return bidReturned; diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index d9d0004e1e0..3cd09d57a35 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -81,6 +81,7 @@ const c_SERVERRESPONSE_B = { cid: '01744fbb521e9fb10ffea926190effea', crid: 'a13cf884e66e7c660afec059c89d98b6', adomain: [ + 'adomain.com' ], }, ], @@ -112,6 +113,7 @@ const c_SERVERRESPONSE_V = { cid: '01744fbb521e9fb10ffea926190effea', crid: 'a13cf884e66e7c660afec059c89d98b6', adomain: [ + 'adomain.com' ], }, ], @@ -385,6 +387,16 @@ describe('Tappx bid adapter', function () { const bids = spec.interpretResponse(emptyServerResponse, c_BIDDERREQUEST_B); expect(bids).to.have.lengthOf(0); }); + + it('receive reponse with adomain', function () { + const bids_B = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); + const bid_B = bids_B[0]; + expect(bid_B.meta.advertiserDomains).to.deep.equal(['adomain.com']); + + const bids_V = spec.interpretResponse(c_SERVERRESPONSE_V, c_BIDDERREQUEST_V); + const bid_V = bids_V[0]; + expect(bid_V.meta.advertiserDomains).to.deep.equal(['adomain.com']); + }); }); /** From 61900ad10e2712a8dfc698e12b9f02767ac2c127 Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Wed, 12 Nov 2025 22:29:14 +0100 Subject: [PATCH 102/147] RTB House Bid Adapter: fix floor price handling (#14112) * RTB House Bid Adapter: add GPP support * Revert "RTB House Bid Adapter: add GPP support" This reverts commit 9bf11f2e9e89b8fcc6e6798e30ec72d692650770. * RTB House Bid Adapter: fix floor price handling * RTB House Bid Adapter: minor spelling fix --- modules/rtbhouseBidAdapter.js | 28 +++++++++++++++----- test/spec/modules/rtbhouseBidAdapter_spec.js | 8 +++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index f909f2302f9..9aec645b715 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -143,18 +143,32 @@ registerBidder(spec); /** * @param {object} slot Ad Unit Params by Prebid - * @returns {number} floor by imp type + * @returns {number|null} floor value, or null if not available */ function applyFloor(slot) { - const floors = []; + // If Price Floors module is available, use it if (typeof slot.getFloor === 'function') { - Object.keys(slot.mediaTypes).forEach(type => { - if (SUPPORTED_MEDIA_TYPES.includes(type)) { - floors.push(slot.getFloor({ currency: DEFAULT_CURRENCY_ARR[0], mediaType: type, size: slot.sizes || '*' })?.floor); + try { + const floor = slot.getFloor({ + currency: DEFAULT_CURRENCY_ARR[0], + mediaType: '*', + size: '*' + }); + + if (floor && floor.currency === DEFAULT_CURRENCY_ARR[0] && !isNaN(parseFloat(floor.floor))) { + return floor.floor; } - }); + } catch (e) { + logError('RTB House: Error calling getFloor:', e); + } } - return floors.length > 0 ? Math.max(...floors) : parseFloat(slot.params.bidfloor); + + // Fallback to bidfloor param if available + if (slot.params.bidfloor && !isNaN(parseFloat(slot.params.bidfloor))) { + return parseFloat(slot.params.bidfloor); + } + + return null; } /** diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index f44ccd1651d..e75190037bd 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -344,17 +344,17 @@ describe('RTBHouseAdapter', () => { expect(data.source.tid).to.equal('bidderrequest-auction-id'); }); - it('should include bidfloor from floor module if avaiable', () => { + it('should include bidfloor from floor module if available', () => { const bidRequest = Object.assign([], bidRequests); - bidRequest[0].getFloor = () => ({floor: 1.22}); + bidRequest[0].getFloor = () => ({floor: 1.22, currency: 'USD'}); const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].bidfloor).to.equal(1.22) }); - it('should use bidfloor from floor module if both floor module and bid floor avaiable', () => { + it('should use bidfloor from floor module if both floor module and bid floor available', () => { const bidRequest = Object.assign([], bidRequests); - bidRequest[0].getFloor = () => ({floor: 1.22}); + bidRequest[0].getFloor = () => ({floor: 1.22, currency: 'USD'}); bidRequest[0].params.bidfloor = 0.01; const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); From 5d3d93a39430640687ef466dc2706f5356b27afa Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Thu, 13 Nov 2025 03:18:07 -0500 Subject: [PATCH 103/147] Sharethrough Bid Adapter: adjust how values are saved on meta prop (#14114) Co-authored-by: Jeff Mahoney --- modules/sharethroughBidAdapter.js | 64 ++-- .../modules/sharethroughBidAdapter_spec.js | 309 ++++++++++-------- 2 files changed, 206 insertions(+), 167 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index e9f653595b7..4d84c21a99e 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,11 +1,11 @@ -import {getDNT} from '../libraries/dnt/index.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { getDNT } from '../libraries/dnt/index.js'; import { handleCookieSync, PID_STORAGE_NAME, prepareSplitImps } from '../libraries/equativUtils/equativUtils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; +import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; @@ -45,8 +45,8 @@ export const sharethroughInternal = { export const converter = ortbConverter({ context: { netRevenue: true, - ttl: 360 - } + ttl: 360, + }, }); export const sharethroughAdapterSpec = { @@ -99,7 +99,7 @@ export const sharethroughAdapterSpec = { test: 0, }; - req.user = firstPartyData.user ?? {} + req.user = firstPartyData.user ?? {}; if (!req.user.ext) req.user.ext = {}; req.user.ext.eids = bidRequests[0].userIdAsEids || []; @@ -108,7 +108,7 @@ export const sharethroughAdapterSpec = { eqtvNetworkId = bidRequests[0].params.equativNetworkId; req.site.publisher = { id: bidRequests[0].params.equativNetworkId, - ...req.site.publisher + ...req.site.publisher, }; const pid = storage.getDataFromLocalStorage(PID_STORAGE_NAME); if (pid) { @@ -190,7 +190,9 @@ export const sharethroughAdapterSpec = { if (propIsTypeArray) { const notAssignable = (!Array.isArray(vidReq[prop]) || vidReq[prop].length === 0) && vidReq[prop]; if (notAssignable) { - logWarn(`${IDENTIFIER_PREFIX} Invalid video request property: "${prop}" must be an array with at least 1 entry. Value supplied: "${vidReq[prop]}". This will not be added to the bid request.`); + logWarn( + `${IDENTIFIER_PREFIX} Invalid video request property: "${prop}" must be an array with at least 1 entry. Value supplied: "${vidReq[prop]}". This will not be added to the bid request.` + ); return; } } @@ -207,24 +209,39 @@ export const sharethroughAdapterSpec = { }; const propertiesToConsider = [ - 'api', 'battr', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay' + 'api', + 'battr', + 'companiontype', + 'delivery', + 'linearity', + 'maxduration', + 'mimes', + 'minduration', + 'placement', + 'playbackmethod', + 'plcmt', + 'protocols', + 'skip', + 'skipafter', + 'skipmin', + 'startdelay', ]; if (!isEqtvTest) { propertiesToConsider.push('companionad'); } - propertiesToConsider.forEach(propertyToConsider => { + propertiesToConsider.forEach((propertyToConsider) => { applyVideoProperty(propertyToConsider, videoRequest, impression); }); } else if (isEqtvTest && nativeRequest) { const nativeImp = converter.toORTB({ bidRequests: [bidReq], - bidderRequest + bidderRequest, }); impression.native = { - ...nativeImp.imp[0].native + ...nativeImp.imp[0].native, }; } else { impression.banner = { @@ -232,7 +249,8 @@ export const sharethroughAdapterSpec = { topframe: inIframe() ? 0 : 1, format: bidReq.sizes.map((size) => ({ w: +size[0], h: +size[1] })), }; - const battr = deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr'); + const battr = + deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr'); if (battr) impression.banner.battr = battr; } @@ -248,7 +266,7 @@ export const sharethroughAdapterSpec = { }) .filter((imp) => !!imp); - let splitImps = [] + let splitImps = []; if (isEqtvTest) { const bid = bidRequests[0]; const currency = config.getConfig('currency.adServerCurrency') || 'USD'; @@ -309,8 +327,8 @@ export const sharethroughAdapterSpec = { brandName: bid.ext?.brandName || null, demandSource: bid.ext?.demandSource || null, dchain: bid.ext?.dchain || null, - primaryCatId: bid.ext?.primaryCatId || null, - secondaryCatIds: bid.ext?.secondaryCatIds || null, + primaryCatId: bid.ext?.primaryCatId || '', + secondaryCatIds: bid.ext?.secondaryCatIds || [], mediaType: bid.ext?.mediaType || null, }, }; @@ -320,7 +338,7 @@ export const sharethroughAdapterSpec = { response.vastXml = bid.adm; } else if (response.mediaType === NATIVE) { response.native = { - ortb: JSON.parse(bid.adm) + ortb: JSON.parse(bid.adm), }; } @@ -339,7 +357,7 @@ export const sharethroughAdapterSpec = { getUserSyncs: (syncOptions, serverResponses, gdprConsent) => { if (isEqtvTest) { - return handleCookieSync(syncOptions, serverResponses, gdprConsent, eqtvNetworkId, storage) + return handleCookieSync(syncOptions, serverResponses, gdprConsent, eqtvNetworkId, storage); } else { const shouldCookieSync = syncOptions.pixelEnabled && deepAccess(serverResponses, '0.body.cookieSyncUrls') !== undefined; @@ -349,13 +367,13 @@ export const sharethroughAdapterSpec = { }, // Empty implementation for prebid core to be able to find it - onTimeout: (data) => { }, + onTimeout: (data) => {}, // Empty implementation for prebid core to be able to find it - onBidWon: (bid) => { }, + onBidWon: (bid) => {}, // Empty implementation for prebid core to be able to find it - onSetTargeting: (bid) => { }, + onSetTargeting: (bid) => {}, }; function getBidRequestFloor(bid) { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 42641ccca1f..dfe9c85e3a3 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -4,9 +4,9 @@ import * as sinon from 'sinon'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config'; import * as utils from 'src/utils'; -import { deepSetValue } from '../../../src/utils.js'; +import * as equativUtils from '../../../libraries/equativUtils/equativUtils.js'; import { getImpIdMap, setIsEqtvTest } from '../../../modules/sharethroughBidAdapter.js'; -import * as equativUtils from '../../../libraries/equativUtils/equativUtils.js' +import { deepSetValue } from '../../../src/utils.js'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); @@ -73,7 +73,7 @@ describe('sharethrough adapter spec', function () { bidder: 'sharethrough', params: { pkey: 111, - equativNetworkId: 73 + equativNetworkId: 73, }, requestId: 'efgh5678', ortb2Imp: { @@ -81,7 +81,7 @@ describe('sharethrough adapter spec', function () { tid: 'zsfgzzg', }, }, - } + }, ]; const videoBidRequests = [ @@ -113,7 +113,7 @@ describe('sharethrough adapter spec', function () { bidder: 'sharethrough', params: { pkey: 111, - equativNetworkIdId: 73 + equativNetworkIdId: 73, }, requestId: 'abcd1234', ortb2Imp: { @@ -121,65 +121,71 @@ describe('sharethrough adapter spec', function () { tid: 'zsgzgzz', }, }, - } + }, ]; const nativeOrtbRequest = { - assets: [{ - id: 0, - required: 1, - title: { - len: 140 - } - }, - { - id: 1, - required: 1, - img: { - type: 3, - w: 300, - h: 600 - } - }, - { - id: 2, - required: 1, - data: { - type: 1 - } - }], + assets: [ + { + id: 0, + required: 1, + title: { + len: 140, + }, + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600, + }, + }, + { + id: 2, + required: 1, + data: { + type: 1, + }, + }, + ], context: 1, - eventtrackers: [{ - event: 1, - methods: [1, 2] - }], + eventtrackers: [ + { + event: 1, + methods: [1, 2], + }, + ], plcmttype: 1, privacy: 1, ver: '1.2', }; - const nativeBidRequests = [{ - bidder: 'sharethrough', - adUnitCode: 'sharethrough_native_42', - bidId: 'bidId3', - sizes: [], - mediaTypes: { - native: { - ...nativeOrtbRequest + const nativeBidRequests = [ + { + bidder: 'sharethrough', + adUnitCode: 'sharethrough_native_42', + bidId: 'bidId3', + sizes: [], + mediaTypes: { + native: { + ...nativeOrtbRequest, + }, }, - }, - nativeOrtbRequest, - params: { - pkey: 777, - equativNetworkId: 73 - }, - requestId: 'sharethrough_native_reqid_42', - ortb2Imp: { - ext: { - tid: 'sharethrough_native_tid_42', + nativeOrtbRequest, + params: { + pkey: 777, + equativNetworkId: 73, + }, + requestId: 'sharethrough_native_reqid_42', + ortb2Imp: { + ext: { + tid: 'sharethrough_native_tid_42', + }, }, }, - }] + ]; beforeEach(() => { config.setConfig({ @@ -334,9 +340,9 @@ describe('sharethrough adapter spec', function () { hp: 1, }, ], - } - } - } + }, + }, + }, }, getFloor: () => ({ currency: 'USD', floor: 42 }), }, @@ -383,14 +389,14 @@ describe('sharethrough adapter spec', function () { mediaTypes: { banner: bannerBidRequests[0].mediaTypes.banner, video: videoBidRequests[0].mediaTypes.video, - native: nativeBidRequests[0].mediaTypes.native + native: nativeBidRequests[0].mediaTypes.native, }, sizes: [], nativeOrtbRequest, bidder: 'sharethrough', params: { pkey: 111, - equativNetworkId: 73 + equativNetworkId: 73, }, requestId: 'efgh5678', ortb2Imp: { @@ -403,8 +409,8 @@ describe('sharethrough adapter spec', function () { return { floor: 1.1 }; } return { floor: 0.9 }; - } - } + }, + }, ]; bidderRequest = { @@ -422,7 +428,7 @@ describe('sharethrough adapter spec', function () { afterEach(() => { setIsEqtvTest(null); - }) + }); describe('buildRequests', function () { describe('top level object', () => { @@ -631,32 +637,38 @@ describe('sharethrough adapter spec', function () { regs: { ext: { dsa: { - 'dsarequired': 1, - 'pubrender': 0, - 'datatopub': 1, - 'transparency': [{ - 'domain': 'good-domain', - 'dsaparams': [1, 2] - }, { - 'domain': 'bad-setup', - 'dsaparams': ['1', 3] - }] - } - } - } - } + dsarequired: 1, + pubrender: 0, + datatopub: 1, + transparency: [ + { + domain: 'good-domain', + dsaparams: [1, 2], + }, + { + domain: 'bad-setup', + dsaparams: ['1', 3], + }, + ], + }, + }, + }, + }; const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; expect(openRtbReq.regs.ext.dsa.dsarequired).to.equal(1); expect(openRtbReq.regs.ext.dsa.pubrender).to.equal(0); expect(openRtbReq.regs.ext.dsa.datatopub).to.equal(1); - expect(openRtbReq.regs.ext.dsa.transparency).to.deep.equal([{ - 'domain': 'good-domain', - 'dsaparams': [1, 2] - }, { - 'domain': 'bad-setup', - 'dsaparams': ['1', 3] - }]); + expect(openRtbReq.regs.ext.dsa.transparency).to.deep.equal([ + { + domain: 'good-domain', + dsaparams: [1, 2], + }, + { + domain: 'bad-setup', + dsaparams: ['1', 3], + }, + ]); }); }); @@ -723,7 +735,7 @@ describe('sharethrough adapter spec', function () { // act const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr; // assert expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); @@ -747,7 +759,7 @@ describe('sharethrough adapter spec', function () { // act const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr; // assert expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); @@ -761,7 +773,7 @@ describe('sharethrough adapter spec', function () { // act const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr; // assert expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); @@ -838,18 +850,34 @@ describe('sharethrough adapter spec', function () { it('should not set a property if no corresponding property is detected on mediaTypes.video', () => { // arrange const propertiesToConsider = [ - 'api', 'battr', 'companionad', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay' - ] + 'api', + 'battr', + 'companionad', + 'companiontype', + 'delivery', + 'linearity', + 'maxduration', + 'mimes', + 'minduration', + 'placement', + 'playbackmethod', + 'plcmt', + 'protocols', + 'skip', + 'skipafter', + 'skipmin', + 'startdelay', + ]; // act - propertiesToConsider.forEach(propertyToConsider => { + propertiesToConsider.forEach((propertyToConsider) => { delete bidRequests[1].mediaTypes.video[propertyToConsider]; }); const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; const videoImp = builtRequest.data.imp[0].video; // assert - propertiesToConsider.forEach(propertyToConsider => { + propertiesToConsider.forEach((propertyToConsider) => { expect(videoImp[propertyToConsider]).to.be.undefined; }); }); @@ -990,30 +1018,27 @@ describe('sharethrough adapter spec', function () { describe('isEqtvTest', () => { it('should set publisher id if equativNetworkId param is present', () => { - const builtRequest = spec.buildRequests(multiImpBidRequests, bidderRequest)[0] - expect(builtRequest.data.site.publisher.id).to.equal(73) - }) + const builtRequest = spec.buildRequests(multiImpBidRequests, bidderRequest)[0]; + expect(builtRequest.data.site.publisher.id).to.equal(73); + }); it('should not set publisher id if equativNetworkId param is not present', () => { const bidRequest = { ...bidRequests[0], params: { ...bidRequests[0].params, - equativNetworkId: undefined - } - } + equativNetworkId: undefined, + }, + }; - const builtRequest = spec.buildRequests([bidRequest], bidderRequest)[0] - expect(builtRequest.data.site.publisher).to.equal(undefined) - }) + const builtRequest = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(builtRequest.data.site.publisher).to.equal(undefined); + }); it('should generate a 14-char id for each imp object', () => { - const request = spec.buildRequests( - bannerBidRequests, - bidderRequest - ); + const request = spec.buildRequests(bannerBidRequests, bidderRequest); - request[0].data.imp.forEach(imp => { + request[0].data.imp.forEach((imp) => { expect(imp.id).to.have.lengthOf(14); }); }); @@ -1022,24 +1047,21 @@ describe('sharethrough adapter spec', function () { const bids = [ { ...bannerBidRequests[0], - getFloor: ({ size }) => ({ floor: size[0] * size[1] / 100_000 }) - } + getFloor: ({ size }) => ({ floor: (size[0] * size[1]) / 100_000 }), + }, ]; - const request = spec.buildRequests( - bids, - bidderRequest - ); + const request = spec.buildRequests(bids, bidderRequest); expect(request[0].data.imp).to.have.lengthOf(2); const firstImp = request[0].data.imp[0]; - expect(firstImp.bidfloor).to.equal(300 * 250 / 100_000); + expect(firstImp.bidfloor).to.equal((300 * 250) / 100_000); expect(firstImp.banner.format).to.have.lengthOf(1); expect(firstImp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); const secondImp = request[0].data.imp[1]; - expect(secondImp.bidfloor).to.equal(300 * 600 / 100_000); + expect(secondImp.bidfloor).to.equal((300 * 600) / 100_000); expect(secondImp.banner.format).to.have.lengthOf(1); expect(secondImp.banner.format[0]).to.deep.equal({ w: 300, h: 600 }); }); @@ -1064,7 +1086,7 @@ describe('sharethrough adapter spec', function () { // expect(secondImp).to.not.have.property('native'); // expect(secondImp).to.have.property('video'); // }); - }) + }); it('should return correct native properties from ORTB converter', () => { if (FEATURES.NATIVE) { @@ -1074,19 +1096,19 @@ describe('sharethrough adapter spec', function () { const asset1 = assets[0]; expect(asset1.id).to.equal(0); expect(asset1.required).to.equal(1); - expect(asset1.title).to.deep.equal({ 'len': 140 }); + expect(asset1.title).to.deep.equal({ len: 140 }); const asset2 = assets[1]; expect(asset2.id).to.equal(1); expect(asset2.required).to.equal(1); - expect(asset2.img).to.deep.equal({ 'type': 3, 'w': 300, 'h': 600 }); + expect(asset2.img).to.deep.equal({ type: 3, w: 300, h: 600 }); const asset3 = assets[2]; expect(asset3.id).to.equal(2); expect(asset3.required).to.equal(1); - expect(asset3.data).to.deep.equal({ 'type': 1 }) + expect(asset3.data).to.deep.equal({ type: 1 }); } - }) + }); }); describe('interpretResponse', function () { @@ -1148,7 +1170,7 @@ describe('sharethrough adapter spec', function () { it('should set requestId from impIdMap when isEqtvTest is true', () => { setIsEqtvTest(true); - request = spec.buildRequests(bannerBidRequests, bidderRequest)[0] + request = spec.buildRequests(bannerBidRequests, bidderRequest)[0]; response = { body: { seatbid: [ @@ -1172,15 +1194,15 @@ describe('sharethrough adapter spec', function () { }; const impIdMap = getImpIdMap(); - impIdMap['aaaabbbbccccdd'] = 'abcd1234' + impIdMap['aaaabbbbccccdd'] = 'abcd1234'; const resp = spec.interpretResponse(response, request)[0]; - expect(resp.requestId).to.equal('abcd1234') - }) + expect(resp.requestId).to.equal('abcd1234'); + }); it('should set ttl when bid.exp is a number > 0', () => { - request = spec.buildRequests(bannerBidRequests, bidderRequest)[0] + request = spec.buildRequests(bannerBidRequests, bidderRequest)[0]; response = { body: { seatbid: [ @@ -1196,7 +1218,7 @@ describe('sharethrough adapter spec', function () { dealid: 'deal', adomain: ['domain.com'], adm: 'markup', - exp: 100 + exp: 100, }, ], }, @@ -1206,10 +1228,10 @@ describe('sharethrough adapter spec', function () { const resp = spec.interpretResponse(response, request)[0]; expect(resp.ttl).to.equal(100); - }) + }); it('should set ttl to 360 when bid.exp is a number <= 0', () => { - request = spec.buildRequests(bannerBidRequests, bidderRequest)[0] + request = spec.buildRequests(bannerBidRequests, bidderRequest)[0]; response = { body: { seatbid: [ @@ -1225,7 +1247,7 @@ describe('sharethrough adapter spec', function () { dealid: 'deal', adomain: ['domain.com'], adm: 'markup', - exp: -1 + exp: -1, }, ], }, @@ -1235,16 +1257,16 @@ describe('sharethrough adapter spec', function () { const resp = spec.interpretResponse(response, request)[0]; expect(resp.ttl).to.equal(360); - }) + }); it('should return correct properties when fledgeAuctionEnabled is true and isEqtvTest is false', () => { - request = spec.buildRequests(bidRequests, bidderRequest)[0] + request = spec.buildRequests(bidRequests, bidderRequest)[0]; response = { body: { ext: { auctionConfigs: { - key: 'value' - } + key: 'value', + }, }, seatbid: [ { @@ -1259,7 +1281,7 @@ describe('sharethrough adapter spec', function () { dealid: 'deal', adomain: ['domain.com'], adm: 'markup', - exp: -1 + exp: -1, }, { id: 'efgh5678', @@ -1271,7 +1293,7 @@ describe('sharethrough adapter spec', function () { dealid: 'deal', adomain: ['domain.com'], adm: 'markup', - exp: -1 + exp: -1, }, ], }, @@ -1281,8 +1303,8 @@ describe('sharethrough adapter spec', function () { const resp = spec.interpretResponse(response, request); expect(resp.bids.length).to.equal(2); - expect(resp.paapi).to.deep.equal({ 'key': 'value' }) - }) + expect(resp.paapi).to.deep.equal({ key: 'value' }); + }); }); describe('video', () => { @@ -1354,9 +1376,9 @@ describe('sharethrough adapter spec', function () { it('should set correct ortb property', () => { const resp = spec.interpretResponse(response, request)[0]; - expect(resp.native.ortb).to.deep.equal({ 'ad': 'ad' }) - }) - }) + expect(resp.native.ortb).to.deep.equal({ ad: 'ad' }); + }); + }); describe('meta object', () => { beforeEach(() => { @@ -1397,8 +1419,8 @@ describe('sharethrough adapter spec', function () { expect(bid.meta.brandName).to.be.null; expect(bid.meta.demandSource).to.be.null; expect(bid.meta.dchain).to.be.null; - expect(bid.meta.primaryCatId).to.be.null; - expect(bid.meta.secondaryCatIds).to.be.null; + expect(bid.meta.primaryCatId).to.equal(''); + expect(bid.meta.secondaryCatIds).to.be.an('array').that.is.empty; expect(bid.meta.mediaType).to.be.null; }); @@ -1482,15 +1504,14 @@ describe('sharethrough adapter spec', function () { it('should call handleCookieSync with correct parameters and return its result', () => { setIsEqtvTest(true); - const expectedResult = [ - { type: 'iframe', url: 'https://sync.example.com' }, - ]; + const expectedResult = [{ type: 'iframe', url: 'https://sync.example.com' }]; - handleCookieSyncStub.returns(expectedResult) + handleCookieSyncStub.returns(expectedResult); - const result = spec.getUserSyncs({ iframeEnabled: true }, - SAMPLE_RESPONSE, - { gdprApplies: true, vendorData: { vendor: { consents: {} } } }); + const result = spec.getUserSyncs({ iframeEnabled: true }, SAMPLE_RESPONSE, { + gdprApplies: true, + vendorData: { vendor: { consents: {} } }, + }); sinon.assert.calledWithMatch( handleCookieSyncStub, From 510f10cec834149fa00188a44e64231000dfa5a9 Mon Sep 17 00:00:00 2001 From: UuqV Date: Thu, 13 Nov 2025 11:01:51 -0500 Subject: [PATCH 104/147] adds nvm path to setup script (#14109) --- .devcontainer/postCreate.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/postCreate.sh b/.devcontainer/postCreate.sh index 7e14a2d200d..257b4905952 100644 --- a/.devcontainer/postCreate.sh +++ b/.devcontainer/postCreate.sh @@ -1,5 +1,8 @@ echo "Post Create Starting" +export NVM_DIR="/usr/local/share/nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + nvm install nvm use npm install gulp-cli -g From 0e4889231c509c8b94e08701e7b04f794ca79b44 Mon Sep 17 00:00:00 2001 From: t-sormonte Date: Thu, 13 Nov 2025 17:07:12 +0100 Subject: [PATCH 105/147] Sparteo Bid Adapter: support new optional query params (#13986) * Sparteo: add required query params to adapter endpoint * Code Review monis0395 --- modules/sparteoBidAdapter.js | 81 +++- test/spec/modules/sparteoBidAdapter_spec.js | 511 ++++++++++++++++++-- 2 files changed, 543 insertions(+), 49 deletions(-) diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index b84b662990c..13cb0198594 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -12,7 +12,7 @@ const BIDDER_CODE = 'sparteo'; const GVLID = 1028; const TTL = 60; const HTTP_METHOD = 'POST'; -const REQUEST_URL = 'https://bid.sparteo.com/auction'; +const REQUEST_URL = `https://bid.sparteo.com/auction?network_id=\${NETWORK_ID}\${SITE_DOMAIN_QUERY}\${APP_DOMAIN_QUERY}\${BUNDLE_QUERY}`; const USER_SYNC_URL_IFRAME = 'https://sync.sparteo.com/sync/iframe.html?from=prebidjs'; let isSynced = window.sparteoCrossfire?.started || false; @@ -25,14 +25,25 @@ const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); - deepSetValue(request, 'site.publisher.ext.params.pbjsVersion', '$prebid.version$'); - - if (bidderRequest.bids[0].params.networkId) { - request.site.publisher.ext.params.networkId = bidderRequest.bids[0].params.networkId; + if (!!(bidderRequest?.ortb2?.site) && !!(bidderRequest?.ortb2?.app)) { + request.site = bidderRequest.ortb2.site; + delete request.app; } - if (bidderRequest.bids[0].params.publisherId) { - request.site.publisher.ext.params.publisherId = bidderRequest.bids[0].params.publisherId; + const hasSite = !!request.site; + const hasApp = !!request.app; + const root = hasSite ? 'site' : (hasApp ? 'app' : null); + + if (root) { + deepSetValue(request, `${root}.publisher.ext.params.pbjsVersion`, '$prebid.version$'); + const networkId = bidderRequest?.bids?.[0]?.params?.networkId; + if (networkId) { + deepSetValue(request, `${root}.publisher.ext.params.networkId`, networkId); + } + const pubId = bidderRequest?.bids?.[0]?.params?.publisherId; + if (pubId) { + deepSetValue(request, `${root}.publisher.ext.params.publisherId`, pubId); + } } return request; @@ -105,6 +116,57 @@ function outstreamRender(bid) { }); } +function replaceMacros(payload, endpoint) { + const networkId = + payload?.site?.publisher?.ext?.params?.networkId ?? + payload?.app?.publisher?.ext?.params?.networkId; + + let siteDomain; + let appDomain; + let bundle; + + if (payload?.site) { + siteDomain = payload.site?.domain; + if (!siteDomain && payload.site?.page) { + try { siteDomain = new URL(payload.site.page).hostname; } catch (e) { } + } + if (siteDomain) { + siteDomain = siteDomain.trim().split('/')[0].split(':')[0].replace(/^www\./, ''); + } else { + logWarn('Domain not found. Missing the site.domain or the site.page field'); + siteDomain = 'unknown'; + } + } else if (payload?.app) { + appDomain = payload.app?.domain || ''; + if (appDomain) { + appDomain = appDomain.trim().split('/')[0].split(':')[0].replace(/^www\./, ''); + } else { + appDomain = 'unknown'; + } + + const raw = payload.app?.bundle ?? ''; + const trimmed = String(raw).trim(); + if (!trimmed || trimmed.toLowerCase() === 'null') { + logWarn('Bundle not found. Missing the app.bundle field.'); + bundle = 'unknown'; + } else { + bundle = trimmed; + } + } + + const macroMap = { + NETWORK_ID: networkId ?? '', + BUNDLE_QUERY: payload?.app ? (bundle ? `&bundle=${encodeURIComponent(bundle)}` : '') : '', + SITE_DOMAIN_QUERY: siteDomain ? `&site_domain=${encodeURIComponent(siteDomain)}` : '', + APP_DOMAIN_QUERY: appDomain ? `&app_domain=${encodeURIComponent(appDomain)}` : '' + }; + + return endpoint.replace( + /\$\{(NETWORK_ID|SITE_DOMAIN_QUERY|APP_DOMAIN_QUERY|BUNDLE_QUERY)\}/g, + (_, key) => String(macroMap[key] ?? '') + ); +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -166,9 +228,12 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const payload = converter.toORTB({bidRequests, bidderRequest}) + const endpoint = bidRequests[0].params.endpoint ? bidRequests[0].params.endpoint : REQUEST_URL; + const url = replaceMacros(payload, endpoint); + return { method: HTTP_METHOD, - url: bidRequests[0].params.endpoint ? bidRequests[0].params.endpoint : REQUEST_URL, + url: url, data: payload }; }, diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js index e65fbbd88ef..12b29d9003b 100644 --- a/test/spec/modules/sparteoBidAdapter_spec.js +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -1,11 +1,11 @@ -import {expect} from 'chai'; +import { expect } from 'chai'; import { deepClone, mergeDeep } from 'src/utils'; -import {spec as adapter} from 'modules/sparteoBidAdapter'; +import { spec as adapter } from 'modules/sparteoBidAdapter'; const CURRENCY = 'EUR'; const TTL = 60; const HTTP_METHOD = 'POST'; -const REQUEST_URL = 'https://bid.sparteo.com/auction'; +const REQUEST_URL = 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=dev.sparteo.com'; const USER_SYNC_URL_IFRAME = 'https://sync.sparteo.com/sync/iframe.html?from=prebidjs'; const VALID_BID_BANNER = { @@ -79,6 +79,7 @@ const VALID_REQUEST_BANNER = { } }], 'site': { + 'domain': 'dev.sparteo.com', 'publisher': { 'ext': { 'params': { @@ -123,6 +124,7 @@ const VALID_REQUEST_VIDEO = { } }], 'site': { + 'domain': 'dev.sparteo.com', 'publisher': { 'ext': { 'params': { @@ -186,6 +188,7 @@ const VALID_REQUEST = { } }], 'site': { + 'domain': 'dev.sparteo.com', 'publisher': { 'ext': { 'params': { @@ -199,17 +202,26 @@ const VALID_REQUEST = { } }; +const ORTB2_GLOBAL = { + site: { + domain: 'dev.sparteo.com' + } +}; + const BIDDER_REQUEST = { - bids: [VALID_BID_BANNER, VALID_BID_VIDEO] -} + bids: [VALID_BID_BANNER, VALID_BID_VIDEO], + ortb2: ORTB2_GLOBAL +}; const BIDDER_REQUEST_BANNER = { - bids: [VALID_BID_BANNER] -} + bids: [VALID_BID_BANNER], + ortb2: ORTB2_GLOBAL +}; const BIDDER_REQUEST_VIDEO = { - bids: [VALID_BID_VIDEO] -} + bids: [VALID_BID_VIDEO], + ortb2: ORTB2_GLOBAL +}; describe('SparteoAdapter', function () { describe('isBidRequestValid', function () { @@ -250,54 +262,49 @@ describe('SparteoAdapter', function () { describe('buildRequests', function () { describe('Check method return', function () { + it('should return the right formatted banner requests', function () { + const request = adapter.buildRequests([VALID_BID_BANNER], BIDDER_REQUEST_BANNER); + delete request.data.id; + + expect(request).to.deep.equal(VALID_REQUEST_BANNER); + }); if (FEATURES.VIDEO) { - it('should return the right formatted requests', function() { + it('should return the right formatted requests', function () { const request = adapter.buildRequests([VALID_BID_BANNER, VALID_BID_VIDEO], BIDDER_REQUEST); delete request.data.id; expect(request).to.deep.equal(VALID_REQUEST); }); - } - - it('should return the right formatted banner requests', function() { - const request = adapter.buildRequests([VALID_BID_BANNER], BIDDER_REQUEST_BANNER); - delete request.data.id; - expect(request).to.deep.equal(VALID_REQUEST_BANNER); - }); - - if (FEATURES.VIDEO) { - it('should return the right formatted video requests', function() { + it('should return the right formatted video requests', function () { const request = adapter.buildRequests([VALID_BID_VIDEO], BIDDER_REQUEST_VIDEO); delete request.data.id; expect(request).to.deep.equal(VALID_REQUEST_VIDEO); }); - } - it('should return the right formatted request with endpoint test', function() { - const endpoint = 'https://bid-test.sparteo.com/auction'; + it('should return the right formatted request with endpoint test', function () { + const endpoint = 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=dev.sparteo.com'; - const bids = mergeDeep(deepClone([VALID_BID_BANNER, VALID_BID_VIDEO]), { - params: { - endpoint: endpoint - } - }); + const bids = deepClone([VALID_BID_BANNER, VALID_BID_VIDEO]); + bids[0].params.endpoint = endpoint; - const requests = mergeDeep(deepClone(VALID_REQUEST)); + const expectedRequest = deepClone(VALID_REQUEST); + expectedRequest.url = endpoint; + expectedRequest.data.imp[0].ext.sparteo.params.endpoint = endpoint; - const request = adapter.buildRequests(bids, BIDDER_REQUEST); - requests.url = endpoint; - delete request.data.id; + const request = adapter.buildRequests(bids, BIDDER_REQUEST); + delete request.data.id; - expect(requests).to.deep.equal(requests); - }); + expect(request).to.deep.equal(expectedRequest); + }); + } }); }); - describe('interpretResponse', function() { + describe('interpretResponse', function () { describe('Check method return', function () { - it('should return the right formatted response', function() { + it('should return the right formatted response', function () { const response = { body: { 'id': '63f4d300-6896-4bdc-8561-0932f73148b1', @@ -458,9 +465,9 @@ describe('SparteoAdapter', function () { }); }); - describe('onBidWon', function() { + describe('onBidWon', function () { describe('Check methods succeed', function () { - it('should not throw error', function() { + it('should not throw error', function () { const bids = [ { requestId: '1a2b3c4d', @@ -500,16 +507,16 @@ describe('SparteoAdapter', function () { } ]; - bids.forEach(function(bid) { + bids.forEach(function (bid) { expect(adapter.onBidWon.bind(adapter, bid)).to.not.throw(); }); }); }); }); - describe('getUserSyncs', function() { + describe('getUserSyncs', function () { describe('Check methods succeed', function () { - it('should return the sync url', function() { + it('should return the sync url', function () { const syncOptions = { 'iframeEnabled': true, 'pixelEnabled': false @@ -531,4 +538,426 @@ describe('SparteoAdapter', function () { }); }); }); + + describe('replaceMacros via buildRequests', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + + it('replaces macros for site traffic (site_domain only)', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + bid.params.networkId = '1234567a-eb1b-1fae-1d23-e1fbaef234cf'; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { + domain: 'site.sparteo.com', + publisher: { domain: 'dev.sparteo.com' } + } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=site.sparteo.com' + ); + }); + + it('uses site.page hostname when site.domain is missing', function () { + const ENDPOINT2 = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}'; + + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT2; + bid.params.networkId = '1234567a-eb1b-1fae-1d23-e1fbaef234cf'; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { + page: 'https://www.dev.sparteo.com:3000/p/some?x=1' + } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=dev.sparteo.com' + ); + }); + + it('omits domain query and leaves network_id empty when neither site nor app is present', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + bid.params.networkId = '1234567a-eb1b-1fae-1d23-e1fbaef234cf'; + + const bidderReq = { bids: [bid], ortb2: {} }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=' + ); + }); + + it('sets site_domain=unknown when site.domain is null', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { + domain: null + } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=unknown' + ); + }); + + it('replaces ${NETWORK_ID} with empty when undefined', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + delete bid.params.networkId; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { + domain: 'dev.sparteo.com' + } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=&site_domain=dev.sparteo.com' + ); + }); + + it('replaces ${NETWORK_ID} with empty when null', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + bid.params.networkId = null; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { + domain: 'dev.sparteo.com' + } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=&site_domain=dev.sparteo.com' + ); + }); + + it('appends &bundle=... and uses app_domain when app.bundle is present', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { + app: { + domain: 'dev.sparteo.com', + bundle: 'com.sparteo.app' + } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&app_domain=dev.sparteo.com&bundle=com.sparteo.app' + ); + }); + + it('does not append &bundle when app is missing; uses site_domain when site exists', function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { domain: 'dev.sparteo.com' } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=dev.sparteo.com' + ); + }); + + it('prefers site over app when both are present', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { + site: { domain: 'site.sparteo.com' }, + app: { domain: 'app.sparteo.com', bundle: 'com.sparteo.app' } + } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=site.sparteo.com' + ); + expect(req.data.site?.publisher?.ext?.params?.networkId).to.equal('1234567a-eb1b-1fae-1d23-e1fbaef234cf'); + expect(req.data.app).to.be.undefined; + }); + + ['', ' ', 'null', 'NuLl'].forEach((val) => { + it(`app bundle "${val}" produces &bundle=unknown`, function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { app: { domain: 'dev.sparteo.com', bundle: val } } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&app_domain=dev.sparteo.com&bundle=unknown' + ); + }); + }); + + it('app domain missing becomes app_domain=unknown while keeping bundle', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { app: { domain: '', bundle: 'com.sparteo.app' } } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&app_domain=unknown&bundle=com.sparteo.app' + ); + }); + + it('uses network_id from app.publisher.ext for app-only traffic', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { app: { domain: 'dev.sparteo.com', bundle: 'com.sparteo.app' } } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.data.site).to.be.undefined; + expect(req.data.app?.publisher?.ext?.params?.networkId).to.equal('1234567a-eb1b-1fae-1d23-e1fbaef234cf'); + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&app_domain=dev.sparteo.com&bundle=com.sparteo.app' + ); + }); + + it('unparsable site.page yields site_domain=unknown', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { site: { page: 'not a url' } } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=unknown' + ); + }); + + it('literal "null" in site.page yields site_domain=unknown', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { site: { domain: '', page: 'null' } } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=unknown' + ); + }); + + it('does not create site on app-only request', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { + bids: [bid], + ortb2: { app: { domain: 'dev.sparteo.com', bundle: 'com.sparteo.app' } } + }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.data.site).to.be.undefined; + expect(req.data.app).to.exist; + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&app_domain=dev.sparteo.com&bundle=com.sparteo.app' + ); + }); + + it('propagates adUnitCode into imp.ext.sparteo.params.adUnitCode', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const req = adapter.buildRequests([bid], { bids: [bid], ortb2: { site: { domain: 'dev.sparteo.com' } } }); + delete req.data.id; + + expect(req.data.imp[0]?.ext?.sparteo?.params?.adUnitCode).to.equal(bid.adUnitCode); + }); + + it('sets pbjsVersion and networkId under site root', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { bids: [bid], ortb2: { site: { domain: 'dev.sparteo.com' } } }; + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + const params = req.data.site?.publisher?.ext?.params; + expect(params?.pbjsVersion).to.equal('$prebid.version$'); + expect(params?.networkId).to.equal('1234567a-eb1b-1fae-1d23-e1fbaef234cf'); + expect(req.data.app?.publisher?.ext?.params?.pbjsVersion).to.be.undefined; + }); + + it('sets pbjsVersion and networkId under app root', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + + const bidderReq = { bids: [bid], ortb2: { app: { domain: 'dev.sparteo.com', bundle: 'com.sparteo.app' } } }; + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + const params = req.data.app?.publisher?.ext?.params; + expect(params?.pbjsVersion).to.equal('$prebid.version$'); + expect(params?.networkId).to.equal('1234567a-eb1b-1fae-1d23-e1fbaef234cf'); + expect(req.data.site).to.be.undefined; + }); + + it('app-only without networkId leaves network_id empty', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${APP_DOMAIN_QUERY}${BUNDLE_QUERY}'; + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + delete bid.params.networkId; + + const bidderReq = { bids: [bid], ortb2: { app: { domain: 'dev.sparteo.com', bundle: 'com.sparteo.app' } } }; + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + 'https://bid.sparteo.com/auction?network_id=&app_domain=dev.sparteo.com&bundle=com.sparteo.app' + ); + }); + }); + + describe('domain normalization (strip www., port, path, trim)', function () { + const ENDPOINT = 'https://bid.sparteo.com/auction?network_id=${NETWORK_ID}${SITE_DOMAIN_QUERY}'; + + const CASES = [ + { + label: 'strips leading "www." from site.domain', + site: { domain: 'www.dev.sparteo.com' }, + expected: 'dev.sparteo.com' + }, + { + label: 'trims whitespace and strips "www."', + site: { domain: ' www.dev.sparteo.com ' }, + expected: 'dev.sparteo.com' + }, + { + label: 'preserves non-"www" prefixes like "www2."', + site: { domain: 'www2.dev.sparteo.com' }, + expected: 'www2.dev.sparteo.com' + }, + { + label: 'removes port from site.page', + site: { page: 'https://dev.sparteo.com:8080/path?q=1' }, + expected: 'dev.sparteo.com' + }, + { + label: 'removes "www." and path from site.page', + site: { page: 'http://www.dev.sparteo.com/p?q=1' }, + expected: 'dev.sparteo.com' + }, + { + label: 'removes port when it appears in site.domain', + site: { domain: 'dev.sparteo.com:8443' }, + expected: 'dev.sparteo.com' + }, + { + label: 'removes accidental path in site.domain', + site: { domain: 'dev.sparteo.com/some/path' }, + expected: 'dev.sparteo.com' + } + ]; + + CASES.forEach(({ label, site, expected }) => { + it(label, function () { + const bid = deepClone(VALID_BID_BANNER); + bid.params.endpoint = ENDPOINT; + const bidderReq = { bids: [bid], ortb2: { site } }; + + const req = adapter.buildRequests([bid], bidderReq); + delete req.data.id; + + expect(req.url).to.equal( + `https://bid.sparteo.com/auction?network_id=1234567a-eb1b-1fae-1d23-e1fbaef234cf&site_domain=${expected}` + ); + }); + }); + }); }); From 7e0d51ba3dc5b4e76ef0218a40532d4ca9f4bf32 Mon Sep 17 00:00:00 2001 From: ClickioTech <163025633+ClickioTech@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:10:47 +0300 Subject: [PATCH 106/147] Clickio Bid Adapter: initial release (#14023) * Clickio Bidder Adapter * Clickio Bid Adapter: update cookie sync URL to new endpoint * Clickio Bid Adapter: add conditional logic for iframe sync response - Updated the getUserSyncs function to return an empty array when iframe sync is disabled, ensuring proper handling of sync options. - Adjusted the corresponding test to verify the new behavior, checking for the updated URL format. * Clickio Bid Adapter: update parameter name from `placementId` to `said` in documentation and tests --------- Co-authored-by: Patrick McCann --- modules/clickioBidAdapter.js | 72 ++++++++ modules/clickioBidAdapter.md | 53 ++++++ test/spec/modules/clickioBidAdapter_spec.js | 177 ++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 modules/clickioBidAdapter.js create mode 100644 modules/clickioBidAdapter.md create mode 100644 test/spec/modules/clickioBidAdapter_spec.js diff --git a/modules/clickioBidAdapter.js b/modules/clickioBidAdapter.js new file mode 100644 index 00000000000..954888291c3 --- /dev/null +++ b/modules/clickioBidAdapter.js @@ -0,0 +1,72 @@ +import {deepSetValue} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'clickio'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.params', bidRequest.params); + return imp; + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + return [{ + method: 'POST', + url: 'https://o.clickiocdn.com/bids', + data + }] + }, + isBidRequestValid(bid) { + return true; + }, + interpretResponse(response, request) { + const bids = converter.fromORTB({response: response.body, request: request.data}).bids; + return bids; + }, + getUserSyncs(syncOptions, _, gdprConsent, uspConsent, gppConsent = {}) { + const { gppString = '', applicableSections = [] } = gppConsent; + const queryParams = []; + + if (gdprConsent) { + if (gdprConsent.gdprApplies !== undefined) { + queryParams.push(`gdpr=${gdprConsent.gdprApplies ? 1 : 0}`); + } + if (gdprConsent.consentString) { + queryParams.push(`gdpr_consent=${gdprConsent.consentString}`); + } + } + if (uspConsent) { + queryParams.push(`us_privacy=${uspConsent}`); + } + queryParams.push(`gpp=${gppString}`); + if (Array.isArray(applicableSections)) { + for (const applicableSection of applicableSections) { + queryParams.push(`gpp_sid=${applicableSection}`); + } + } + if (syncOptions.iframeEnabled) { + return [ + { + type: 'iframe', + url: `https://o.clickiocdn.com/cookie_sync_html?${queryParams.join('&')}` + } + ]; + } else { + return []; + } + } +}; + +registerBidder(spec); diff --git a/modules/clickioBidAdapter.md b/modules/clickioBidAdapter.md new file mode 100644 index 00000000000..5d3d41488ff --- /dev/null +++ b/modules/clickioBidAdapter.md @@ -0,0 +1,53 @@ +--- +layout: bidder +title: Clickio +description: Clickio Bidder Adapter +biddercode: clickio +media_types: banner +gdpr_supported: true +usp_supported: true +gpp_supported: true +schain_supported: true +coppa_supported: true +userId: all +--- + +# Overview + +``` +Module Name: Clickio Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@clickio.com +``` + +### Description + +The Clickio bid adapter connects to Clickio's demand platform using OpenRTB 2.5 standard. This adapter supports banner advertising. + +The Clickio bidding adapter requires initial setup before use. Please contact us at [support@clickio.com](mailto:support@clickio.com). +To get started, simply replace the ``said`` with the ID assigned to you. + +### Test Parameters + +```javascript +var adUnits = [ + { + code: 'clickio-banner-ad', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [ + { + bidder: 'clickio', + params: { + said: 'test', + } + } + ] + } +]; +``` \ No newline at end of file diff --git a/test/spec/modules/clickioBidAdapter_spec.js b/test/spec/modules/clickioBidAdapter_spec.js new file mode 100644 index 00000000000..d35ded253e1 --- /dev/null +++ b/test/spec/modules/clickioBidAdapter_spec.js @@ -0,0 +1,177 @@ +import { expect } from 'chai'; +import { spec } from 'modules/clickioBidAdapter.js'; + +describe('clickioBidAdapter', function () { + const DEFAULT_BANNER_BID_REQUESTS = [ + { + adUnitCode: 'div-banner-id', + bidId: 'bid-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90], + ], + }, + }, + bidder: 'clickio', + params: {}, + requestId: 'request-123', + } + ]; + + const DEFAULT_BANNER_BIDDER_REQUEST = { + bidderCode: 'clickio', + bids: DEFAULT_BANNER_BID_REQUESTS, + }; + + const SAMPLE_RESPONSE = { + body: { + id: '12h712u7-k22g-8124-ab7a-h268s22dy271', + seatbid: [ + { + bid: [ + { + id: '1bh7jku7-ko2g-8654-ab72-h268abcde271', + impid: 'bid-123', + price: 1.5, + adm: '
Test Ad
', + adomain: ['example.com'], + cid: '1242512', + crid: '535231', + w: 300, + h: 250, + mtype: 1, + }, + ], + seat: '4212', + }, + ], + cur: 'USD', + }, + }; + + describe('isBidRequestValid', () => { + it('should return true for any valid bid request', () => { + const bidRequest = { + params: {}, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('should build a valid request object', () => { + const request = spec.buildRequests( + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + ); + + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf(1); + expect(request[0].method).to.equal('POST'); + expect(request[0].url).to.equal('https://o.clickiocdn.com/bids'); + expect(request[0].data).to.be.an('object'); + }); + + it('should include imp with ext.params from bidRequest', () => { + const bidRequestsWithParams = [ + { + ...DEFAULT_BANNER_BID_REQUESTS[0], + params: { + said: '123', + someParam: 'value' + } + } + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequestsWithParams }; + const request = spec.buildRequests(bidRequestsWithParams, bidderRequest)[0]; + + expect(request.data.imp).to.be.an('array'); + expect(request.data.imp[0].ext.params).to.deep.equal({ + said: '123', + someParam: 'value' + }); + }); + }); + + describe('interpretResponse', () => { + it('should return valid bids from ORTB response', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(SAMPLE_RESPONSE, request); + + expect(bids).to.be.an('array'); + expect(bids).to.have.lengthOf(1); + + const bid = bids[0]; + expect(bid.requestId).to.exist; + expect(bid.cpm).to.equal(1.5); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.contain('
Test Ad
'); + expect(bid.creativeId).to.equal('535231'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(30); + }); + + it('should return empty array if no bids in response', () => { + const emptyResponse = { + body: { + id: '12h712u7-k22g-8124-ab7a-h268s22dy271', + seatbid: [], + cur: 'USD', + }, + }; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(emptyResponse, request); + + expect(bids).to.be.an('array'); + expect(bids).to.be.empty; + }); + }); + + describe('getUserSyncs', () => { + it('should return iframe user sync', () => { + const syncOptions = { iframeEnabled: true }; + const syncs = spec.getUserSyncs(syncOptions); + + expect(syncs).to.be.an('array'); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include('https://o.clickiocdn.com/cookie_sync_html'); + }); + + it('should include GDPR parameters when gdprConsent is provided', () => { + const syncOptions = { iframeEnabled: true }; + const gdprConsent = { + gdprApplies: true, + consentString: 'test-consent-string' + }; + const syncs = spec.getUserSyncs(syncOptions, null, gdprConsent); + + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=test-consent-string'); + }); + + it('should include USP consent when uspConsent is provided', () => { + const syncOptions = { iframeEnabled: true }; + const uspConsent = '1YNN'; + const syncs = spec.getUserSyncs(syncOptions, null, null, uspConsent); + + expect(syncs[0].url).to.include('us_privacy=1YNN'); + }); + + it('should include GPP parameters when gppConsent is provided', () => { + const syncOptions = { iframeEnabled: true }; + const gppConsent = { + gppString: 'DBACNYA~CPXxRfAPXxR', + applicableSections: [7, 8] + }; + const syncs = spec.getUserSyncs(syncOptions, null, null, null, gppConsent); + + expect(syncs[0].url).to.include('gpp=DBACNYA~CPXxRfAPXxR'); + expect(syncs[0].url).to.include('gpp_sid=7'); + expect(syncs[0].url).to.include('gpp_sid=8'); + }); + }); +}); From 17d91111db19b0ee5712330849a00dde8c072042 Mon Sep 17 00:00:00 2001 From: Keith Candiotti Date: Thu, 13 Nov 2025 11:40:15 -0500 Subject: [PATCH 107/147] optimeraRTD: updated scorefile fetching logic (#14101) --- modules/optimeraRtdProvider.js | 14 ++++++++------ test/spec/modules/optimeraRtdProvider_spec.js | 4 ++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index 3a46184e9c1..153fbea2980 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -56,9 +56,6 @@ export let transmitWithBidRequests = 'allow'; /** @type {Object} */ export let optimeraTargeting = {}; -/** @type {boolean} */ -export let fetchScoreFile = true; - /** @type {RtdSubmodule} */ export const optimeraSubmodule = { name: 'optimeraRTD', @@ -84,7 +81,6 @@ export function init(moduleConfig) { if (_moduleParams.transmitWithBidRequests) { transmitWithBidRequests = _moduleParams.transmitWithBidRequests; } - setScoresURL(); return true; } logError('Optimera clientID is missing in the Optimera RTD configuration.'); @@ -111,9 +107,9 @@ export function setScoresURL() { if (scoresURL !== newScoresURL) { scoresURL = newScoresURL; - fetchScoreFile = true; + return true; } else { - fetchScoreFile = false; + return false; } } @@ -125,6 +121,12 @@ export function setScoresURL() { * @param {object} userConsent */ export function fetchScores(reqBidsConfigObj, callback, config, userConsent) { + // If setScoresURL returns false, no need to re-fetch the score file + if (!setScoresURL()) { + callback(); + return; + } + // Else, fetch the score file const ajax = ajaxBuilder(); ajax(scoresURL, { success: (res, req) => { diff --git a/test/spec/modules/optimeraRtdProvider_spec.js b/test/spec/modules/optimeraRtdProvider_spec.js index adcc84dcf73..d3165bd3c7d 100644 --- a/test/spec/modules/optimeraRtdProvider_spec.js +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -37,6 +37,7 @@ describe('Optimera RTD score file URL is properly set for v0', () => { }] }; optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScoresURL(); optimeraRTD.setScores(); expect(optimeraRTD.apiVersion).to.equal('v0'); expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost%3A9876/context.html.js'); @@ -54,6 +55,7 @@ describe('Optimera RTD score file URL is properly set for v0', () => { }] }; optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScoresURL(); optimeraRTD.setScores(); expect(optimeraRTD.apiVersion).to.equal('v0'); expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost%3A9876/context.html.js'); @@ -72,6 +74,7 @@ describe('Optimera RTD score file URL is properly set for v0', () => { }] }; optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScoresURL(); optimeraRTD.setScores(); expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost%3A9876/context.html.js'); }); @@ -91,6 +94,7 @@ describe('Optimera RTD score file URL is properly set for v1', () => { }] }; optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScoresURL(); optimeraRTD.setScores(); expect(optimeraRTD.apiVersion).to.equal('v1'); expect(optimeraRTD.scoresURL).to.equal('https://v1.oapi26b.com/api/products/scores?c=9999&h=localhost:9876&p=/context.html&s=de'); From 197cd8945740b345cdc71e237a78ef64cad9b0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Thu, 13 Nov 2025 20:23:28 +0200 Subject: [PATCH 108/147] Cointraffic Bid Adapter: Added device information to payload (#14120) * endpoint update * added device information to payload --- modules/cointrafficBidAdapter.js | 34 +++++++++++++++---- .../modules/cointrafficBidAdapter_spec.js | 32 +++++++++++------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index 4920a6cc974..d9394253497 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -3,6 +3,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js' +import { getDNT } from '../libraries/dnt/index.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -12,7 +14,7 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j */ const BIDDER_CODE = 'cointraffic'; -const ENDPOINT_URL = 'https://apps-pbd.ctraffic.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps.adsgravity.io/v1/request/prebid'; const DEFAULT_CURRENCY = 'EUR'; const ALLOWED_CURRENCIES = [ 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', @@ -43,11 +45,24 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { - const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); - const currency = - config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || - getCurrencyFromBidderRequest(bidderRequest) || - DEFAULT_CURRENCY; + const sizes = parseSizesInput(bidRequest.params.size || bidRequest.mediaTypes.banner.sizes); + const { width, height } = getViewportSize(); + + const getCurrency = () => { + return config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || + getCurrencyFromBidderRequest(bidderRequest) || + DEFAULT_CURRENCY; + } + + const getLanguage = () => { + return navigator && navigator.language + ? navigator.language.indexOf('-') !== -1 + ? navigator.language.split('-')[0] + : navigator.language + : ''; + } + + const currency = getCurrency(); if (ALLOWED_CURRENCIES.indexOf(currency) === -1) { logError('Currency is not supported - ' + currency); @@ -60,6 +75,13 @@ export const spec = { sizes: sizes, bidId: bidRequest.bidId, referer: bidderRequest.refererInfo.ref, + device: { + width: width, + height: height, + user_agent: bidRequest.params.ua || navigator.userAgent, + dnt: getDNT() ? 1 : 0, + language: getLanguage(), + }, }; return { diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js index e90216d9612..78fbfadddc4 100644 --- a/test/spec/modules/cointrafficBidAdapter_spec.js +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -9,7 +9,7 @@ import * as utils from 'src/utils.js' * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ -const ENDPOINT_URL = 'https://apps-pbd.ctraffic.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps.adsgravity.io/v1/request/prebid'; describe('cointrafficBidAdapter', function () { describe('isBidRequestValid', function () { @@ -20,9 +20,13 @@ describe('cointrafficBidAdapter', function () { placementId: 'testPlacementId' }, adUnitCode: 'adunit-code', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + }, + }, bidId: 'bidId12345', bidderRequestId: 'bidderRequestId12345', auctionId: 'auctionId12345' @@ -42,9 +46,13 @@ describe('cointrafficBidAdapter', function () { placementId: 'testPlacementId' }, adUnitCode: 'adunit-code', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + }, + }, bidId: 'bidId12345', bidderRequestId: 'bidderRequestId12345', auctionId: 'auctionId12345' @@ -55,9 +63,13 @@ describe('cointrafficBidAdapter', function () { placementId: 'testPlacementId' }, adUnitCode: 'adunit-code2', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + }, + }, bidId: 'bidId67890"', bidderRequestId: 'bidderRequestId67890', auctionId: 'auctionId12345' From 095c6395f593c40814731d16194ebd65c52364f3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 13 Nov 2025 13:23:44 -0500 Subject: [PATCH 109/147] Core: fix spurious validation warnings on mediaType / ortb2Imp (#14099) --- src/prebid.ts | 2 +- test/spec/banner_spec.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/prebid.ts b/src/prebid.ts index 96b53e541af..3d5883cf294 100644 --- a/src/prebid.ts +++ b/src/prebid.ts @@ -158,7 +158,7 @@ export function syncOrtb2(adUnit, mediaType) { deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue); } else if (ortbFieldValue === undefined) { deepSetValue(adUnit, `ortb2Imp.${mediaType}.${key}`, mediaTypesFieldValue); - } else { + } else if (!deepEqual(mediaTypesFieldValue, ortbFieldValue)) { logWarn(`adUnit ${adUnit.code}: specifies conflicting ortb2Imp.${mediaType}.${key} and mediaTypes.${mediaType}.${key}, the latter will be ignored`, adUnit); deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue); } diff --git a/test/spec/banner_spec.js b/test/spec/banner_spec.js index fcf56ad6e10..f60f2023194 100644 --- a/test/spec/banner_spec.js +++ b/test/spec/banner_spec.js @@ -127,6 +127,23 @@ describe('banner', () => { assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting btype'); }); + it('should not warn if fields match', () => { + const adUnit = { + mediaTypes: { + banner: { + format: [{wratio: 1, hratio: 1}] + } + }, + ortb2Imp: { + banner: { + format: [{wratio: 1, hratio: 1}] + } + } + } + syncOrtb2(adUnit, 'banner'); + sinon.assert.notCalled(logWarnSpy); + }) + it('should omit sync if mediaType not present on adUnit', () => { const adUnit = { mediaTypes: { From 76ddae9cd95263a00e61e06e187c8ff44004ac49 Mon Sep 17 00:00:00 2001 From: topOnFens <118327552+topOnFens@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:34:14 +0800 Subject: [PATCH 110/147] Add TopOn adapter (#14072) --- modules/toponBidAdapter.js | 170 ++++++++++++++++++++++ modules/toponBidAdapter.md | 29 ++++ test/spec/modules/toponBidAdapter_spec.js | 122 ++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 modules/toponBidAdapter.js create mode 100644 modules/toponBidAdapter.md create mode 100644 test/spec/modules/toponBidAdapter_spec.js diff --git a/modules/toponBidAdapter.js b/modules/toponBidAdapter.js new file mode 100644 index 00000000000..263069b7f34 --- /dev/null +++ b/modules/toponBidAdapter.js @@ -0,0 +1,170 @@ +import { logWarn, generateUUID } from "../src/utils.js"; +import { registerBidder } from "../src/adapters/bidderFactory.js"; +import { BANNER } from "../src/mediaTypes.js"; +import { ortbConverter } from "../libraries/ortbConverter/converter.js"; + +const PREBID_VERSION = "$prebid.version$"; +const BIDDER_CODE = "topon"; +const LOG_PREFIX = "TopOn"; +const GVLID = 1305; +const ENDPOINT = "https://web-rtb.anyrtb.com/ortb/prebid"; +const DEFAULT_TTL = 360; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_TTL, + currency: "USD", + }, + imp(buildImp, bidRequest, context) { + const mediaType = + bidRequest.mediaType || Object.keys(bidRequest.mediaTypes || {})[0]; + + if (mediaType === "banner") { + const sizes = bidRequest.mediaTypes.banner.sizes; + return { + id: bidRequest.bidId, + banner: { + format: sizes.map(([w, h]) => ({ w, h })), + }, + tagid: bidRequest.adUnitCode, + }; + } + + return null; + }, + request(buildRequest, imps, bidderRequest, context) { + const requestId = + bidderRequest.bidderRequestId || + bidderRequest.auctionId || + generateUUID(); + const ortb2 = bidderRequest.ortb2 || {}; + + return { + id: requestId, + imp: imps, + site: { + page: ortb2.site?.page || bidderRequest.refererInfo?.page, + domain: ortb2.site?.domain || location.hostname, + }, + device: ortb2.device || {}, + ext: { + prebid: { + channel: { + version: PREBID_VERSION, + source: "pbjs", + }, + }, + }, + source: { + ext: { + prebid: 1, + }, + }, + }; + }, + bidResponse(buildBidResponse, bid, context) { + return buildBidResponse(bid, context); + }, + response(buildResponse, bidResponses, ortbResponse, context) { + return buildResponse(bidResponses, ortbResponse, context); + }, + overrides: { + imp: { + bidfloor: false, + extBidfloor: false, + }, + bidResponse: { + native: false, + }, + }, +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + isBidRequestValid: (bid) => { + if (!(bid && bid.params)) { + return false; + } + const { pubid } = bid.params || {}; + if (!pubid) { + return false; + } else if (typeof pubid !== "string") { + return false; + } + return true; + }, + buildRequests: (validBidRequests, bidderRequest) => { + const { pubid } = bidderRequest?.bids?.[0]?.params || {}; + const ortbRequest = converter.toORTB({ validBidRequests, bidderRequest }); + + const url = ENDPOINT + "?pubid=" + pubid; + return { + method: "POST", + url, + data: ortbRequest, + }; + }, + interpretResponse: (response, request) => { + if (!response.body || typeof response.body !== "object") { + return; + } + + const { id, seatbid: seatbids } = response.body; + if (id && seatbids) { + seatbids.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + let height = bid.h; + let width = bid.w; + const isBanner = bid.mtype === 1; + if ( + (!height || !width) && + request.data && + request.data.imp && + request.data.imp.length > 0 + ) { + request.data.imp.forEach((req) => { + if (bid.impid === req.id) { + if (isBanner) { + let bannerHeight = 1; + let bannerWidth = 1; + if (req.banner.format && req.banner.format.length > 0) { + bannerHeight = req.banner.format[0].h; + bannerWidth = req.banner.format[0].w; + } + height = bannerHeight; + width = bannerWidth; + } else { + height = 1; + width = 1; + } + } + }); + bid.w = width; + bid.h = height; + } + }); + }); + } + + const { bids } = converter.fromORTB({ + response: response.body, + request: request.data, + }); + + return bids; + }, + getUserSyncs: ( + syncOptions, + responses, + gdprConsent, + uspConsent, + gppConsent + ) => {}, + onBidWon: (bid) => { + logWarn(`[${LOG_PREFIX}] Bid won: ${JSON.stringify(bid)}`); + }, +}; +registerBidder(spec); diff --git a/modules/toponBidAdapter.md b/modules/toponBidAdapter.md new file mode 100644 index 00000000000..e42ad438fb8 --- /dev/null +++ b/modules/toponBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: TopOn Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@toponad.net +``` + +# Description + +TopOn Bid Adapter for Prebid.js + +# Sample Banner Ad Unit: For Publishers + +``` +var adUnits = [{ + code: 'test-div', + sizes: [ + [300, 250], + [728, 90] + ], + bids: [{ + bidder: 'topon', + params: { + pubid: 'pub-uuid', // required, must be a string, not an integer or other js type. + } + }] +}]; +``` diff --git a/test/spec/modules/toponBidAdapter_spec.js b/test/spec/modules/toponBidAdapter_spec.js new file mode 100644 index 00000000000..bf717e4b847 --- /dev/null +++ b/test/spec/modules/toponBidAdapter_spec.js @@ -0,0 +1,122 @@ +import { expect } from "chai"; +import { spec } from "modules/toponBidAdapter.js"; +import * as utils from "src/utils.js"; + +describe("TopOn Adapter", function () { + const PREBID_VERSION = "$prebid.version$"; + const BIDDER_CODE = "topon"; + + let bannerBid = { + bidder: BIDDER_CODE, + params: { + pubid: "pub-uuid", + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + }; + + const validBidRequests = [bannerBid]; + const bidderRequest = { + bids: [bannerBid], + }; + + const bannerResponse = { + bid: [ + { + id: "6e976fc683e543d892160ee7d6f057d8", + impid: "1fabbf3c-b5e4-4b7d-9956-8112f92c1076", + price: 7.906274762781043, + nurl: "https://127.0.0.1:1381/prebid_tk?...", + burl: "https://127.0.0.1:1381/prebid_tk?...", + lurl: "https://127.0.0.1:1381/prebid_tk?...", + adm: `
✅ TopOn Mock Ad
300x250 🚫
`, + adid: "Ad538d326a-47f1-4c22-80f0-67684a713898", + cid: "110", + crid: "Creative32666aba-b5d3-4074-9ad1-d1702e9ba22b", + exp: 1800, + ext: {}, + mtype: 1, + }, + ], + }; + + const response = { + body: { + cur: "USD", + id: "aa2653ff-bd37-4fef-8085-2e444347af8c", + seatbid: [bannerResponse], + }, + }; + + it("should properly expose spec attributes", function () { + expect(spec.code).to.equal(BIDDER_CODE); + expect(spec.supportedMediaTypes).to.exist.and.to.be.an("array"); + expect(spec.isBidRequestValid).to.be.a("function"); + expect(spec.buildRequests).to.be.a("function"); + expect(spec.interpretResponse).to.be.a("function"); + }); + + describe("Bid validations", () => { + it("should return true if publisherId is present in params", () => { + const isValid = spec.isBidRequestValid(validBidRequests[0]); + expect(isValid).to.equal(true); + }); + + it("should return false if publisherId is missing", () => { + const bid = utils.deepClone(validBidRequests[0]); + delete bid.params.pubid; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it("should return false if publisherId is not of type string", () => { + const bid = utils.deepClone(validBidRequests[0]); + bid.params.pubid = 10000; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + }); + + describe("Requests", () => { + it("should correctly build an ORTB Bid Request", () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request).to.be.an("object"); + expect(request.method).to.equal("POST"); + expect(request.data).to.exist; + expect(request.data).to.be.an("object"); + expect(request.data.id).to.be.an("string"); + expect(request.data.id).to.not.be.empty; + }); + + it("should include prebid flag in request", () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.data.ext).to.have.property("prebid"); + expect(request.data.ext.prebid).to.have.property("channel"); + expect(request.data.ext.prebid.channel).to.deep.equal({ + version: PREBID_VERSION, + source: "pbjs", + }); + expect(request.data.source.ext.prebid).to.equal(1); + }); + }); + + describe("Response", () => { + it("should parse banner adm and set bidResponse.ad, width, and height", () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + response.body.seatbid[0].bid[0].impid = request.data.imp[0].id; + const bidResponses = spec.interpretResponse(response, request); + + expect(bidResponses).to.be.an("array"); + expect(bidResponses[0]).to.exist; + expect(bidResponses[0].ad).to.exist; + expect(bidResponses[0].mediaType).to.equal("banner"); + expect(bidResponses[0].width).to.equal(300); + expect(bidResponses[0].height).to.equal(250); + }); + }); +}); From 02a23a06ceeccdf0abbccaa09e1121d8fec1223f Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 17 Nov 2025 11:55:58 -0500 Subject: [PATCH 111/147] Permutive modules: define gvl id (#14131) * Permutive modules: centralize gvl id * Set gvlid to null in permutiveIdentityManagerIdSystem * Set gvlid to null in permutiveRtdProvider.json * Set gvlid to null for Permutive components * Fix JSON formatting by adding missing newline * Fix missing newline in permutiveIdentityManagerIdSystem.json Add missing newline at the end of the JSON file. * Update permutiveRtdProvider.json --- metadata/modules.json | 2 +- metadata/modules/permutiveIdentityManagerIdSystem.json | 2 +- metadata/modules/permutiveRtdProvider.json | 2 +- modules/permutiveIdentityManagerIdSystem.js | 2 ++ modules/permutiveRtdProvider.js | 2 ++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/metadata/modules.json b/metadata/modules.json index bbca2363d99..f1258462fee 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -6092,4 +6092,4 @@ "gvlid": null } ] -} \ No newline at end of file +} diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index e8fc0cd1fac..ce7b56fecbc 100644 --- a/metadata/modules/permutiveIdentityManagerIdSystem.json +++ b/metadata/modules/permutiveIdentityManagerIdSystem.json @@ -10,4 +10,4 @@ "aliasOf": null } ] -} \ No newline at end of file +} diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 0e675450fa8..691d87b6af8 100644 --- a/metadata/modules/permutiveRtdProvider.json +++ b/metadata/modules/permutiveRtdProvider.json @@ -9,4 +9,4 @@ "disclosureURL": null } ] -} \ No newline at end of file +} diff --git a/modules/permutiveIdentityManagerIdSystem.js b/modules/permutiveIdentityManagerIdSystem.js index 5dc12d44edb..f3849644445 100644 --- a/modules/permutiveIdentityManagerIdSystem.js +++ b/modules/permutiveIdentityManagerIdSystem.js @@ -10,6 +10,7 @@ import {prefixLog, safeJSONParse} from '../src/utils.js' */ const MODULE_NAME = 'permutiveIdentityManagerId' +const PERMUTIVE_GVLID = 361 const PERMUTIVE_ID_DATA_STORAGE_KEY = 'permutive-prebid-id' const ID5_DOMAIN = 'id5-sync.com' @@ -80,6 +81,7 @@ export const permutiveIdentityManagerIdSubmodule = { * @type {string} */ name: MODULE_NAME, + gvlid: PERMUTIVE_GVLID, /** * decode the stored id value for passing to bid requests diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index bb06d2d138e..886dc8b3b5f 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -17,6 +17,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; */ const MODULE_NAME = 'permutive' +const PERMUTIVE_GVLID = 361 const logger = prefixLog('[PermutiveRTD]') @@ -466,6 +467,7 @@ let permutiveSDKInRealTime = false /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, + gvlid: PERMUTIVE_GVLID, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { const completeBidRequestData = () => { logger.logInfo(`Request data updated`) From 4ec515d8aba59d44820ffee7b7283b7f26c03aba Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 17 Nov 2025 11:58:06 -0500 Subject: [PATCH 112/147] CI: split tests into smaller chunks (#14126) * CI: increase karma browserNoActivityTimeout * try decreasing * increase test timeout * Fix fintezaAnalytics tests * Increase number of test chunks * Fix number of chunks * Extract save-wdir action * use matrix * simplify * fix chunk def * fix concurrency key * fix test cmd * Output concurrency * Fix concurrency key again * only save coverage if it exists * adjust max-parallel * collect coverage results * temp disable e2e * rename serialize to browserstack * Use wdir to get access to local action * fix github_output-s * set up node, fix coverage cache keys * temporarily enable coverage on nofeatures * skip browserstack step when not using browserstack * key prefix * debug output * debug * remove some debug output * script syntax * matrix output * adjust matrix output * fixes * use artifacts directly * cleanup outputs * use artifacts instead of cache * consolidate * specify path * debug * build when there is no build * include hidden files * overwrite artifacts * try save/load * adjust load * try skipping untar * more debug * Remove debug output * cleanup dependencies * Adjust timeouts * adjust overall timeout * adjust timeouts again * fix finteza tests * adjust timeouts * use build name from env * Clear browserstack sessions * Always clean up sessions * sessions cannot be terminated via api * adjust capture timeout * skip build when there is no build cmd * increase retries * adjust timeouts * add chunk no in build name * Fix coveralls * isolate browserstack tests * Revert "isolate browserstack tests" This reverts commit 2ebfa9d2b4dc4b4baf71e448149e6321ff218da6. --- .github/actions/load/action.yml | 30 +++ .github/actions/save/action.yml | 19 ++ .../actions/wait-for-browserstack/action.yml | 10 +- .github/workflows/run-tests.yml | 180 ++++++++++++++++++ .github/workflows/run-unit-tests.yml | 109 ----------- .github/workflows/test-chunk.yml | 103 ---------- .github/workflows/test.yml | 106 +++-------- gulpfile.js | 2 +- karma.conf.maker.js | 10 +- .../modules/fintezaAnalyticsAdapter_spec.js | 47 ++--- wdio.conf.js | 3 +- 11 files changed, 291 insertions(+), 328 deletions(-) create mode 100644 .github/actions/load/action.yml create mode 100644 .github/actions/save/action.yml create mode 100644 .github/workflows/run-tests.yml delete mode 100644 .github/workflows/run-unit-tests.yml delete mode 100644 .github/workflows/test-chunk.yml diff --git a/.github/actions/load/action.yml b/.github/actions/load/action.yml new file mode 100644 index 00000000000..85ea9009a61 --- /dev/null +++ b/.github/actions/load/action.yml @@ -0,0 +1,30 @@ +name: Load working directory +description: Load working directory saved with "actions/save" +inputs: + name: + description: The name used with actions/save + +runs: + using: 'composite' + steps: + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: 'Clear working directory' + shell: bash + run: | + rm -r ./* + + - name: Download artifact + uses: actions/download-artifact@v5 + with: + path: '${{ runner.temp }}' + name: '${{ inputs.name }}' + + - name: 'Untar working directory' + shell: bash + run: | + tar -xf '${{ runner.temp }}/${{ inputs.name }}.tar' . + diff --git a/.github/actions/save/action.yml b/.github/actions/save/action.yml new file mode 100644 index 00000000000..3dd8c1f6ea0 --- /dev/null +++ b/.github/actions/save/action.yml @@ -0,0 +1,19 @@ +name: Save working directory +description: Save working directory, preserving permissions +inputs: + name: + description: a name to reference with actions/load + +runs: + using: 'composite' + steps: + - name: Tar working directory + shell: bash + run: | + tar -cf "${{ runner.temp }}/${{ inputs.name }}.tar" . + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + path: '${{ runner.temp }}/${{ inputs.name }}.tar' + name: ${{ inputs.name }} + overwrite: true diff --git a/.github/actions/wait-for-browserstack/action.yml b/.github/actions/wait-for-browserstack/action.yml index 63d24b87f88..12ad89d7008 100644 --- a/.github/actions/wait-for-browserstack/action.yml +++ b/.github/actions/wait-for-browserstack/action.yml @@ -1,18 +1,10 @@ name: Wait for browserstack sessions description: Wait until enough browserstack sessions have become available -inputs: - BROWSERSTACK_USER_NAME: - description: "Browserstack user name" - BROWSERSTACK_ACCESS_KEY: - description: "Browserstack access key" runs: using: 'composite' steps: - - env: - BROWSERSTACK_USERNAME: ${{ inputs.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ inputs.BROWSERSTACK_ACCESS_KEY }} - shell: bash + - shell: bash run: | while status=$(curl -u "${BROWSERSTACK_USERNAME}:${BROWSERSTACK_ACCESS_KEY}" \ diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000000..f76ab69a0ec --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,180 @@ +name: Run unit tests +on: + workflow_call: + inputs: + chunks: + description: Number of chunks to split tests into + required: false + type: number + default: 1 + build-cmd: + description: Build command, run once + required: false + type: string + test-cmd: + description: Test command, run once per chunk + required: true + type: string + browserstack: + description: If true, set up browserstack environment and adjust concurrency + required: false + type: boolean + timeout: + description: Timeout on test run + required: false + type: number + default: 10 + outputs: + coverage: + description: Artifact name for coverage results + value: ${{ jobs.collect-coverage.outputs.coverage }} + secrets: + BROWSERSTACK_USER_NAME: + description: "Browserstack user name" + BROWSERSTACK_ACCESS_KEY: + description: "Browserstack access key" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + chunks: ${{ steps.chunks.outputs.chunks }} + wdir: ${{ inputs.build-cmd && format('build-{0}', inputs.build-cmd) || 'source' }} + steps: + - name: Checkout + if: ${{ inputs.build-cmd }} + uses: actions/checkout@v5 + - name: Restore source + if: ${{ inputs.build-cmd }} + uses: ./.github/actions/load + with: + name: source + + - name: Build + if: ${{ inputs.build-cmd }} + run: ${{ inputs.build-cmd }} + + - name: 'Save working directory' + if: ${{ inputs.build-cmd }} + uses: ./.github/actions/save + with: + name: build-${{ inputs.build-cmd }} + + - name: Define chunks + id: chunks + run: | + echo 'chunks=[ '$(seq --separator=, 1 1 ${{ inputs.chunks }})' ]' >> "$GITHUB_OUTPUT" + + + + run-tests: + needs: build + strategy: + fail-fast: false + max-parallel: ${{ inputs.browserstack && 1 || inputs.chunks }} + matrix: + chunk-no: ${{ fromJSON(needs.build.outputs.chunks) }} + + name: "Test chunk ${{ matrix.chunk-no }}" + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USER_NAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + TEST_CHUNKS: ${{ inputs.chunks }} + TEST_CHUNK: ${{ matrix.chunk-no }} + outputs: + coverage: ${{ steps.coverage.outputs.coverage }} + concurrency: + # The following generates 'browserstack-' when inputs.browserstack is true, and a hopefully unique ID otherwise + # Ideally we'd like to serialize browserstack access across all workflows, but github's max queue length is only 1 + # (cfr. https://github.com/orgs/community/discussions/12835) + # so we add the run_id to serialize only within one push / pull request (which has the effect of queueing e2e and unit tests) + group: ${{ inputs.browserstack && 'browser' || github.run_id }}${{ inputs.browserstack && 'stac' || inputs.test-cmd }}${{ inputs.browserstack && 'k' || matrix.chunk-no }}-${{ github.run_id }} + cancel-in-progress: false + + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Restore source + uses: ./.github/actions/load + with: + name: ${{ needs.build.outputs.wdir }} + + - name: 'BrowserStack Env Setup' + if: ${{ inputs.browserstack }} + uses: 'browserstack/github-actions/setup-env@master' + with: + username: ${{ secrets.BROWSERSTACK_USER_NAME}} + access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + build-name: Run ${{github.run_id}}, attempt ${{ github.run_attempt }}, chunk ${{ matrix.chunk-no }}, ref ${{ github.event_name == 'pull_request_target' && format('PR {0}', github.event.pull_request.number) || github.ref }}, ${{ inputs.test-cmd }} + + - name: 'BrowserStackLocal Setup' + if: ${{ inputs.browserstack }} + uses: 'browserstack/github-actions/setup-local@master' + with: + local-testing: start + local-identifier: random + + - name: 'Wait for browserstack' + if: ${{ inputs.browserstack }} + uses: ./.github/actions/wait-for-browserstack + + - name: Run tests + uses: nick-fields/retry@v3 + with: + timeout_minutes: ${{ inputs.timeout }} + max_attempts: 3 + command: ${{ inputs.test-cmd }} + + - name: 'BrowserStackLocal Stop' + if: ${{ inputs.browserstack }} + uses: 'browserstack/github-actions/setup-local@master' + with: + local-testing: stop + + - name: 'Check for coverage' + id: 'coverage' + run: | + if [ -d "./build/coverage" ]; then + echo 'coverage=true' >> "$GITHUB_OUTPUT"; + fi + + - name: 'Save coverage result' + if: ${{ steps.coverage.outputs.coverage }} + uses: actions/upload-artifact@v4 + with: + name: coverage-partial-${{inputs.test-cmd}}-${{ matrix.chunk-no }} + path: ./build/coverage + overwrite: true + + collect-coverage: + if: ${{ needs.run-tests.outputs.coverage }} + needs: [build, run-tests] + name: 'Collect coverage results' + runs-on: ubuntu-latest + outputs: + coverage: coverage-complete-${{ inputs.test-cmd }} + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Restore source + uses: ./.github/actions/load + with: + name: ${{ needs.build.outputs.wdir }} + + - name: Download coverage results + uses: actions/download-artifact@v5 + with: + path: ./build/coverage + pattern: coverage-partial-${{ inputs.test-cmd }}-* + merge-multiple: true + + - name: 'Save working directory' + uses: ./.github/actions/save + with: + name: coverage-complete-${{ inputs.test-cmd }} + diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml deleted file mode 100644 index 60e0713a552..00000000000 --- a/.github/workflows/run-unit-tests.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Run unit tests -on: - workflow_call: - inputs: - build-cmd: - description: Build command, run once - required: true - type: string - test-cmd: - description: Test command, run once per chunk - required: true - type: string - serialize: - description: If true, allow only one concurrent chunk (see note on concurrency below) - required: false - type: boolean - outputs: - wdir: - description: Cache key for the working directory after running tests - value: ${{ jobs.chunk-4.outputs.wdir }} - secrets: - BROWSERSTACK_USER_NAME: - description: "Browserstack user name" - BROWSERSTACK_ACCESS_KEY: - description: "Browserstack access key" - -jobs: - build: - name: Build - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - - - name: Fetch source - uses: actions/cache/restore@v4 - with: - path: . - key: source-${{ github.run_id }} - fail-on-cache-miss: true - - - name: Build - run: ${{ inputs.build-cmd }} - - - name: Cache build output - uses: actions/cache/save@v4 - with: - path: . - key: build-${{ inputs.build-cmd }}-${{ github.run_id }} - - - name: Verify cache - uses: actions/cache/restore@v4 - with: - path: . - key: build-${{ inputs.build-cmd }}-${{ github.run_id }} - lookup-only: true - fail-on-cache-miss: true - - chunk-1: - needs: build - name: Run tests (chunk 1 of 4) - uses: ./.github/workflows/test-chunk.yml - with: - chunk-no: 1 - wdir: build-${{ inputs.build-cmd }}-${{ github.run_id }} - cmd: ${{ inputs.test-cmd }} - serialize: ${{ inputs.serialize }} - secrets: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - chunk-2: - name: Run tests (chunk 2 of 4) - needs: chunk-1 - uses: ./.github/workflows/test-chunk.yml - with: - chunk-no: 2 - wdir: ${{ needs.chunk-1.outputs.wdir }} - cmd: ${{ inputs.test-cmd }} - serialize: ${{ inputs.serialize }} - secrets: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - chunk-3: - name: Run tests (chunk 3 of 4) - needs: chunk-2 - uses: ./.github/workflows/test-chunk.yml - with: - chunk-no: 3 - wdir: ${{ needs.chunk-2.outputs.wdir }} - cmd: ${{ inputs.test-cmd }} - serialize: ${{ inputs.serialize }} - secrets: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - chunk-4: - name: Run tests (chunk 4 of 4) - needs: chunk-3 - uses: ./.github/workflows/test-chunk.yml - with: - chunk-no: 4 - wdir: ${{ needs.chunk-3.outputs.wdir }} - cmd: ${{ inputs.test-cmd }} - serialize: ${{ inputs.serialize }} - secrets: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} diff --git a/.github/workflows/test-chunk.yml b/.github/workflows/test-chunk.yml deleted file mode 100644 index 900bde960ee..00000000000 --- a/.github/workflows/test-chunk.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Test chunk -on: - workflow_call: - inputs: - serialize: - required: false - type: boolean - cmd: - required: true - type: string - chunk-no: - required: true - type: number - wdir: - required: true - type: string - outputs: - wdir: - description: "Cache key for the working directory after running tests" - value: test-${{ inputs.cmd }}-${{ inputs.chunk-no }}-${{ github.run_id }} - secrets: - BROWSERSTACK_USER_NAME: - description: "Browserstack user name" - BROWSERSTACK_ACCESS_KEY: - description: "Browserstack access key" - -concurrency: - # The following generates 'browserstack-' when inputs.serialize is true, and a hopefully unique ID otherwise - # Ideally we'd like to serialize browserstack access across all workflows, but github's max queue length is only 1 - # (cfr. https://github.com/orgs/community/discussions/12835) - # so we add the run_id to serialize only within one push / pull request (which has the effect of queueing e2e and unit tests) - group: ${{ inputs.serialize && 'browser' || github.run_id }}${{ inputs.serialize && 'stack' || inputs.cmd }}-${{ github.run_id }} - cancel-in-progress: false - -jobs: - test: - name: "Test chunk ${{ inputs.chunk-no }}" - env: - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - TEST_CHUNKS: 4 - TEST_CHUNK: ${{ inputs.chunk-no }} - runs-on: ubuntu-latest - steps: - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - - - name: Restore working directory - id: restore-dir - uses: actions/cache/restore@v4 - with: - path: . - key: ${{ inputs.wdir }} - fail-on-cache-miss: true - - - name: 'BrowserStack Env Setup' - uses: 'browserstack/github-actions/setup-env@master' - with: - username: ${{ secrets.BROWSERSTACK_USER_NAME}} - access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - - - name: 'BrowserStackLocal Setup' - uses: 'browserstack/github-actions/setup-local@master' - with: - local-testing: start - local-identifier: random - - - name: 'Wait for browserstack' - if: ${{ inputs.serialize }} - uses: ./.github/actions/wait-for-browserstack - with: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - - - name: Run tests - uses: nick-fields/retry@v3 - with: - timeout_minutes: 8 - max_attempts: 3 - command: ${{ inputs.cmd }} - - - name: 'BrowserStackLocal Stop' - uses: 'browserstack/github-actions/setup-local@master' - with: - local-testing: stop - - - name: Save working directory - uses: actions/cache/save@v4 - with: - path: . - key: test-${{ inputs.cmd }}-${{ inputs.chunk-no }}-${{ github.run_id }} - - - name: Verify cache - uses: actions/cache/restore@v4 - with: - path: . - key: test-${{ inputs.cmd }}-${{ inputs.chunk-no }}-${{ github.run_id }} - lookup-only: true - fail-on-cache-miss: true - - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d243a67ca5d..0ab3aa8249e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,35 +55,22 @@ jobs: - name: Install dependencies run: npm ci - - name: Cache source - uses: actions/cache/save@v4 + - name: 'Save working directory' + uses: ./.github/actions/save with: - path: . - key: source-${{ github.run_id }} - - - name: Verify cache - uses: actions/cache/restore@v4 - with: - path: . - key: source-${{ github.run_id }} - lookup-only: true - fail-on-cache-miss: true + name: source lint: name: "Run linter" needs: checkout runs-on: ubuntu-latest steps: - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' + - name: Checkout + uses: actions/checkout@v5 - name: Restore source - uses: actions/cache/restore@v4 + uses: ./.github/actions/load with: - path: . - key: source-${{ github.run_id }} - fail-on-cache-miss: true + name: source - name: lint run: | npx eslint @@ -91,90 +78,53 @@ jobs: test-no-features: name: "Unit tests (all features disabled)" needs: checkout - uses: ./.github/workflows/run-unit-tests.yml + uses: ./.github/workflows/run-tests.yml with: + chunks: 8 build-cmd: npx gulp precompile-all-features-disabled test-cmd: npx gulp test-all-features-disabled-nobuild - serialize: false + browserstack: false secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} test: name: "Unit tests (all features enabled + coverage)" needs: checkout - uses: ./.github/workflows/run-unit-tests.yml + uses: ./.github/workflows/run-tests.yml with: + chunks: 8 build-cmd: npx gulp precompile test-cmd: npx gulp test-only-nobuild --browserstack - serialize: true + browserstack: true secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} test-e2e: - name: "End-to-end tests" + name: End-to-end tests needs: checkout - runs-on: ubuntu-latest - concurrency: - # see test-chunk.yml for notes on concurrency groups - group: browserstack-${{ github.run_id }} - cancel-in-progress: false - env: - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USER_NAME }} + uses: ./.github/workflows/run-tests.yml + with: + test-cmd: npx gulp e2e-test + browserstack: true + timeout: 15 + secrets: + BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - steps: - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - - name: Restore source - uses: actions/cache/restore@v4 - with: - path: . - key: source-${{ github.run_id }} - fail-on-cache-miss: true - - - name: 'BrowserStack Env Setup' - uses: 'browserstack/github-actions/setup-env@master' - with: - username: ${{ secrets.BROWSERSTACK_USER_NAME}} - access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - - - name: 'BrowserStackLocal Setup' - uses: 'browserstack/github-actions/setup-local@master' - with: - local-testing: start - local-identifier: random - - - name: 'Wait for browserstack' - uses: ./.github/actions/wait-for-browserstack - with: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - - - name: Run tests - uses: nick-fields/retry@v3 - with: - timeout_minutes: 20 - max_attempts: 3 - command: npx gulp e2e-test - - - name: 'BrowserStackLocal Stop' - uses: 'browserstack/github-actions/setup-local@master' - with: - local-testing: stop coveralls: name: Update coveralls needs: [checkout, test] runs-on: ubuntu-latest steps: - - name: Restore working directory - uses: actions/cache/restore@v4 + - name: Checkout + uses: actions/checkout@v5 + + - name: Restore source + uses: ./.github/actions/load with: - path: . - key: ${{ needs.test.outputs.wdir }} - fail-on-cache-miss: true + name: ${{ needs.test.outputs.coverage }} + - name: Coveralls uses: coverallsapp/github-action@v2 with: diff --git a/gulpfile.js b/gulpfile.js index dc1cc51f62e..845b1836733 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,7 +39,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const {precompile, babelPrecomp} = require('./gulp.precompilation.js'); -const TEST_CHUNKS = 4; +const TEST_CHUNKS = 8; // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules var explicitModules = [ diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 1068e9828d8..f825b8eac4c 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -88,7 +88,7 @@ function setBrowsers(karmaConf, browserstack) { karmaConf.browserStack = { username: process.env.BROWSERSTACK_USERNAME, accessKey: process.env.BROWSERSTACK_ACCESS_KEY, - build: 'Prebidjs Unit Tests ' + new Date().toLocaleString() + build: process.env.BROWSERSTACK_BUILD_NAME } if (process.env.TRAVIS) { karmaConf.browserStack.startTunnel = false; @@ -174,10 +174,10 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: !watchMode, - browserDisconnectTimeout: 1e5, // default 2000 - browserNoActivityTimeout: 1e5, // default 10000 - captureTimeout: 3e5, // default 60000, - browserDisconnectTolerance: 3, + browserDisconnectTimeout: 1e4, + browserNoActivityTimeout: 3e4, + captureTimeout: 2e4, + browserDisconnectTolerance: 5, concurrency: 5, // browserstack allows us 5 concurrent sessions plugins: plugins diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index eaf5b5f40c2..ed9d7b1639b 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -75,12 +75,13 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(EVENTS.BID_REQUESTED, bidRequest); - expect(server.requests.length).to.equal(1); + const reqs = server.requests.filter(req => req.url.startsWith('https://content.mql5.com')); + expect(reqs.length).to.eql(1); - expect(server.requests[0].method).to.equal('GET'); - expect(server.requests[0].withCredentials).to.equal(true); + expect(reqs[0].method).to.equal('GET'); + expect(reqs[0].withCredentials).to.equal(true); - const url = parseUrl(server.requests[0].url); + const url = parseUrl(reqs[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -88,8 +89,9 @@ describe('finteza analytics adapter', function () { expect(url.search.id).to.equal(clientId); expect(url.search.fz_uniq).to.equal(uniqCookie); expect(decodeURIComponent(url.search.event)).to.equal(`Bid Request ${bidderCode.toUpperCase()}`); - - sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + sinon.assert.calledWith(fntzAnalyticsAdapter.track, sinon.match({ + eventType: EVENTS.BID_REQUESTED + })); }); }); @@ -117,13 +119,12 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(EVENTS.BID_RESPONSE, bidResponse); + const reqs = server.requests.filter(req => req.url.startsWith('https://content.mql5.com')); + expect(reqs.length).to.equal(2); + expect(reqs[0].method).to.equal('GET'); + expect(reqs[0].withCredentials).to.equal(true); - expect(server.requests[0].method).to.equal('GET'); - expect(server.requests[0].withCredentials).to.equal(true); - - expect(server.requests.length).to.equal(2); - - let url = parseUrl(server.requests[0].url); + let url = parseUrl(reqs[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -134,10 +135,10 @@ describe('finteza analytics adapter', function () { expect(url.search.value).to.equal(String(cpm)); expect(url.search.unit).to.equal('usd'); - expect(server.requests[1].method).to.equal('GET'); - expect(server.requests[1].withCredentials).to.equal(true); + expect(reqs[1].method).to.equal('GET'); + expect(reqs[1].withCredentials).to.equal(true); - url = parseUrl(server.requests[1].url); + url = parseUrl(reqs[1].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -148,7 +149,9 @@ describe('finteza analytics adapter', function () { expect(url.search.value).to.equal(String(timeToRespond)); expect(url.search.unit).to.equal('ms'); - sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + sinon.assert.calledWith(fntzAnalyticsAdapter.track, sinon.match({ + eventType: EVENTS.BID_RESPONSE + })); }); }); @@ -172,12 +175,13 @@ describe('finteza analytics adapter', function () { // Emit the events with the "real" arguments events.emit(EVENTS.BID_WON, bidWon); - expect(server.requests[0].method).to.equal('GET'); - expect(server.requests[0].withCredentials).to.equal(true); + const reqs = server.requests.filter((req) => req.url.startsWith('https://content.mql5.com')); + expect(reqs[0].method).to.equal('GET'); + expect(reqs[0].withCredentials).to.equal(true); - expect(server.requests.length).to.equal(1); + expect(reqs.length).to.equal(1); - const url = parseUrl(server.requests[0].url); + const url = parseUrl(reqs[0].url); expect(url.protocol).to.equal('https'); expect(url.hostname).to.equal('content.mql5.com'); @@ -188,8 +192,7 @@ describe('finteza analytics adapter', function () { expect(url.search.value).to.equal(String(cpm)); expect(url.search.unit).to.equal('usd'); - // 1 Finteza event + 1 Clean.io event - sinon.assert.callCount(fntzAnalyticsAdapter.track, 2); + sinon.assert.calledWith(fntzAnalyticsAdapter.track, sinon.match({eventType: EVENTS.BID_WON})) }); }); diff --git a/wdio.conf.js b/wdio.conf.js index d23fecd0b15..53ccd216b69 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -1,4 +1,5 @@ const shared = require('./wdio.shared.conf.js'); +const process = require('process'); const browsers = Object.fromEntries( Object.entries(require('./browsers.json')) @@ -28,7 +29,7 @@ function getCapabilities() { osVersion: browser.os_version, networkLogs: true, consoleLogs: 'verbose', - buildName: `Prebidjs E2E (${browser.browser} ${browser.browser_version}) ${new Date().toLocaleString()}` + buildName: process.env.BROWSERSTACK_BUILD_NAME }, acceptInsecureCerts: true, }); From 2ccc9eedcc40bbf2637df5ad0cbd005ceb57ebb5 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:59:48 +0200 Subject: [PATCH 113/147] Attekmi: rename alias from Marlinads to Amcom (#14138) Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index e779fd8d43d..4848d80d538 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -25,7 +25,7 @@ const ALIASES = { 'addigi': {area: '1', pid: '425'}, 'jambojar': {area: '1', pid: '426'}, 'anzu': {area: '1', pid: '445'}, - 'marlinads': {area: '1', pid: '397'}, + 'amcom': {area: '1', pid: '397'}, }; const BASE_URLS = { @@ -41,7 +41,7 @@ const BASE_URLS = { 'jambojar': 'https://jambojar-prebid.attekmi.co/pbjs', 'jambojar-apac': 'https://jambojar-apac-prebid.attekmi.co/pbjs', 'anzu': 'https://anzu-prebid.attekmi.co/pbjs', - 'marlinads': 'https://marlinads-prebid.attekmi.co/pbjs', + 'amcom': 'https://amcom-prebid.attekmi.co/pbjs', }; const adapterState = {}; From 9a062d985a92b466382fb7af54c3d74b41581db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petric=C4=83=20Nanc=C4=83?= Date: Mon, 17 Nov 2025 19:00:36 +0200 Subject: [PATCH 114/147] sevioBidAdapter_bugfix: Send all sizes instead of just maxSize (#14133) * Send all sizes instead of just maxSize * Added tests to cover modifs in the sizes that we are sending --- modules/sevioBidAdapter.js | 14 ++++----- test/spec/modules/sevioBidAdapter_spec.js | 37 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index 78ee46bf0f1..4551bd2c76c 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -179,9 +179,12 @@ export const spec = { return bidRequests.map((bidRequest) => { const isNative = detectAdType(bidRequest)?.toLowerCase() === 'native'; - const size = bidRequest.mediaTypes?.banner?.sizes[0] || bidRequest.mediaTypes?.native?.sizes[0] || []; - const width = size[0]; - const height = size[1]; + const adSizes = bidRequest.mediaTypes?.banner?.sizes || bidRequest.mediaTypes?.native?.sizes || []; + const formattedSizes = Array.isArray(adSizes) + ? adSizes + .filter(size => Array.isArray(size) && size.length === 2) + .map(([width, height]) => ({ width, height })) + : []; const originalAssets = bidRequest.mediaTypes?.native?.ortb?.assets || []; // convert icon to img type 1 @@ -211,10 +214,7 @@ export const spec = { })).filter(eid => eid.source && eid.id), ads: [ { - maxSize: { - width: width, - height: height, - }, + sizes: formattedSizes, referenceId: bidRequest.params.referenceId, tagId: bidRequest.params.zone, type: detectAdType(bidRequest), diff --git a/test/spec/modules/sevioBidAdapter_spec.js b/test/spec/modules/sevioBidAdapter_spec.js index 2f4d3bf337d..a7a56d798a9 100644 --- a/test/spec/modules/sevioBidAdapter_spec.js +++ b/test/spec/modules/sevioBidAdapter_spec.js @@ -408,5 +408,42 @@ describe('sevioBidAdapter', function () { Object.defineProperty(perfTop, 'timing', { configurable: true, value: undefined }); } }); + + it('handles multiple sizes correctly', function () { + const multiSizeBidRequests = [ + { + bidder: 'sevio', + params: { zone: 'zoneId' }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90], + [160, 600], + ] + } + }, + bidId: 'multi123', + } + ]; + + const bidderRequests = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'https://example.com', + stack: ['https://example.com'] + } + }; + + const request = spec.buildRequests(multiSizeBidRequests, bidderRequests); + const sizes = request[0].data.ads[0].sizes; + + expect(sizes).to.deep.equal([ + { width: 300, height: 250 }, + { width: 728, height: 90 }, + { width: 160, height: 600 }, + ]); + }); }); }); From bdefe0941ddf4e93c898847f186003767f0912c3 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:07:17 +0200 Subject: [PATCH 115/147] Attekmi: add Adastra Tech alias (#14141) Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 4848d80d538..9565f318ead 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -26,6 +26,7 @@ const ALIASES = { 'jambojar': {area: '1', pid: '426'}, 'anzu': {area: '1', pid: '445'}, 'amcom': {area: '1', pid: '397'}, + 'adastra': {area: '1', pid: '33'}, }; const BASE_URLS = { @@ -42,6 +43,7 @@ const BASE_URLS = { 'jambojar-apac': 'https://jambojar-apac-prebid.attekmi.co/pbjs', 'anzu': 'https://anzu-prebid.attekmi.co/pbjs', 'amcom': 'https://amcom-prebid.attekmi.co/pbjs', + 'adastra': 'https://adastra-prebid.attekmi.co/pbjs', }; const adapterState = {}; From ff8f4f98b56d2907c486b494d8f70982f862402b Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Tue, 18 Nov 2025 21:41:36 +0100 Subject: [PATCH 116/147] Nexx360 Bid Adapter: buildImp fix (#14139) * buildImp fix * Update modules/nexx360BidAdapter.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update bidderVersion from '7.0' to '7.1' on test * Add divId to nexx360 configuration in tests --------- Co-authored-by: Gabriel Chicoye Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libraries/nexx360Utils/index.ts | 4 ++-- modules/nexx360BidAdapter.ts | 13 +++++-------- test/spec/modules/nexx360BidAdapter_spec.js | 6 +++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libraries/nexx360Utils/index.ts b/libraries/nexx360Utils/index.ts index dec3ce47056..0b8ed3fd719 100644 --- a/libraries/nexx360Utils/index.ts +++ b/libraries/nexx360Utils/index.ts @@ -5,7 +5,7 @@ import { INSTREAM, OUTSTREAM } from '../../src/video.js'; import { BANNER, MediaType, NATIVE, VIDEO } from '../../src/mediaTypes.js'; import { BidResponse, VideoBidResponse } from '../../src/bidfactory.js'; import { StorageManager } from '../../src/storageManager.js'; -import { BidRequest, ORTBRequest, ORTBResponse } from '../../src/prebid.public.js'; +import { BidRequest, ORTBImp, ORTBRequest, ORTBResponse } from '../../src/prebid.public.js'; import { AdapterResponse, ServerResponse } from '../../src/adapters/bidderFactory.js'; const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -122,7 +122,7 @@ export const createRenderer = ( return renderer; }; -export const enrichImp = (imp, bidRequest:BidRequest) => { +export const enrichImp = (imp:ORTBImp, bidRequest:BidRequest): ORTBImp => { deepSetValue(imp, 'tagid', bidRequest.adUnitCode); deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); const divId = bidRequest.params.divId || bidRequest.adUnitCode; diff --git a/modules/nexx360BidAdapter.ts b/modules/nexx360BidAdapter.ts index 393bea2dbab..b5c27bd288b 100644 --- a/modules/nexx360BidAdapter.ts +++ b/modules/nexx360BidAdapter.ts @@ -13,7 +13,7 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '7.0'; +const BIDDER_VERSION = '7.1'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; @@ -35,6 +35,7 @@ type Nexx360BidParams = RequireAtLeastOne<{ divId?: string; allBids?: boolean; customId?: string; + bidders?: Record; }, "tagId" | "placement">; declare module '../src/adUnits' { @@ -87,7 +88,7 @@ const converter = ortbConverter({ 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, context) { + imp(buildImp, bidRequest: BidRequest, context) { let imp:ORTBImp = buildImp(bidRequest, context); imp = enrichImp(imp, bidRequest); const divId = bidRequest.params.divId || bidRequest.adUnitCode; @@ -99,14 +100,10 @@ const converter = ortbConverter({ deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); } - if (bidRequest.params.tagId) deepSetValue(imp, 'ext.nexx360.tagId', bidRequest.params.tagId); - if (bidRequest.params.placement) deepSetValue(imp, 'ext.nexx360.placement', bidRequest.params.placement); - if (bidRequest.params.videoTagId) deepSetValue(imp, 'ext.nexx360.videoTagId', bidRequest.params.videoTagId); + 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); - if (bidRequest.params.allBids) deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); - if (bidRequest.params.nativeTagId) deepSetValue(imp, 'ext.nexx360.nativeTagId', bidRequest.params.nativeTagId); - if (bidRequest.params.customId) deepSetValue(imp, 'ext.nexx360.customId', bidRequest.params.customId); return imp; }, request(buildRequest, imps, bidderRequest, context) { diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index a5e8ab03d1a..d3ba872946f 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -308,6 +308,9 @@ describe('Nexx360 bid adapter tests', () => { }, 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', @@ -329,6 +332,7 @@ describe('Nexx360 bid adapter tests', () => { divId: 'div-2-abcd', nexx360: { placement: 'testPlacement', + divId: 'div-2-abcd', allBids: true, }, }, @@ -340,7 +344,7 @@ describe('Nexx360 bid adapter tests', () => { version: requestContent.ext.version, source: 'prebid.js', pageViewId: requestContent.ext.pageViewId, - bidderVersion: '7.0', + bidderVersion: '7.1', localStorage: { amxId: 'abcdef'}, sessionId: requestContent.ext.sessionId, requestCounter: 0, From 7f46e3f9af4993dad3de263dd5637f2cf2663a1b Mon Sep 17 00:00:00 2001 From: kazutoshi-uekawa-muneee <114455471+kazutoshi-uekawa-muneee@users.noreply.github.com> Date: Wed, 19 Nov 2025 05:45:19 +0900 Subject: [PATCH 117/147] fix(adapter): align uniquest_widgetBidAdapter file name with docs biddercode (#14043) --- ...iquestWidgetBidAdapter.js => uniquest_widgetBidAdapter.js} | 0 ...iquestWidgetBidAdapter.md => uniquest_widgetBidAdapter.md} | 0 ...etBidAdapter_spec.js => uniquest_widgetBidAdapter_spec.js} | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename modules/{uniquestWidgetBidAdapter.js => uniquest_widgetBidAdapter.js} (100%) rename modules/{uniquestWidgetBidAdapter.md => uniquest_widgetBidAdapter.md} (100%) rename test/spec/modules/{uniquestWidgetBidAdapter_spec.js => uniquest_widgetBidAdapter_spec.js} (96%) diff --git a/modules/uniquestWidgetBidAdapter.js b/modules/uniquest_widgetBidAdapter.js similarity index 100% rename from modules/uniquestWidgetBidAdapter.js rename to modules/uniquest_widgetBidAdapter.js diff --git a/modules/uniquestWidgetBidAdapter.md b/modules/uniquest_widgetBidAdapter.md similarity index 100% rename from modules/uniquestWidgetBidAdapter.md rename to modules/uniquest_widgetBidAdapter.md diff --git a/test/spec/modules/uniquestWidgetBidAdapter_spec.js b/test/spec/modules/uniquest_widgetBidAdapter_spec.js similarity index 96% rename from test/spec/modules/uniquestWidgetBidAdapter_spec.js rename to test/spec/modules/uniquest_widgetBidAdapter_spec.js index b487fdd8de4..55a06a976bc 100644 --- a/test/spec/modules/uniquestWidgetBidAdapter_spec.js +++ b/test/spec/modules/uniquest_widgetBidAdapter_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { spec } from 'modules/uniquestWidgetBidAdapter.js'; +import { spec } from 'modules/uniquest_widgetBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; const ENDPOINT = 'https://adpb.ust-ad.com/hb/prebid/widgets'; -describe('UniquestWidgetAdapter', function () { +describe('uniquest_widgetBidAdapter', function () { const adapter = newBidder(spec); describe('inherited functions', function () { From 1b288d2d9ccaf26b6937532eb3a18163b7c8db32 Mon Sep 17 00:00:00 2001 From: FreeWheelVIS Date: Tue, 18 Nov 2025 13:30:10 -0800 Subject: [PATCH 118/147] FWSSP Adapter: update schain serialization logic and add fallback for missing videoassetid (#14135) * FWSSP Adapter: update schain serialization logic and add fallback for missing videoassetid * FWSSP Adapter: fix linting in fwsspBidAdapter_spec - indentation * FWSSP Adapter: fix schain unit test to use dynamic pbjs.version in vclr * FWSSP Adapter: fix schain unit test missing 'prebid' in vclr --------- Co-authored-by: Josh Kraskin --- modules/fwsspBidAdapter.js | 58 +++++--- modules/fwsspBidAdapter.md | 57 +++++++- test/spec/modules/fwsspBidAdapter_spec.js | 167 +++++++++++++++++----- 3 files changed, 219 insertions(+), 63 deletions(-) diff --git a/modules/fwsspBidAdapter.js b/modules/fwsspBidAdapter.js index 42f649d618b..3a07402eb44 100644 --- a/modules/fwsspBidAdapter.js +++ b/modules/fwsspBidAdapter.js @@ -22,7 +22,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid(bid) { - return !!(bid.params.serverUrl && bid.params.networkId && bid.params.profile && bid.params.siteSectionId && bid.params.videoAssetId); + return !!(bid.params.serverUrl && bid.params.networkId && bid.params.profile && bid.params.siteSectionId); }, /** @@ -47,9 +47,9 @@ export const spec = { const buildRequest = (currentBidRequest, bidderRequest) => { const globalParams = constructGlobalParams(currentBidRequest); const keyValues = constructKeyValues(currentBidRequest, bidderRequest); - const slotParams = constructSlotParams(currentBidRequest); - const dataString = constructDataString(globalParams, keyValues, slotParams); + const serializedSChain = constructSupplyChain(currentBidRequest, bidderRequest); + const dataString = constructDataString(globalParams, keyValues, serializedSChain, slotParams); return { method: 'GET', url: currentBidRequest.params.serverUrl, @@ -58,6 +58,19 @@ export const spec = { }; } + const constructSupplyChain = (currentBidRequest, bidderRequest) => { + // Add schain object + let schain = deepAccess(bidderRequest, 'ortb2.source.schain'); + if (!schain) { + schain = deepAccess(bidderRequest, 'ortb2.source.ext.schain'); + } + if (!schain) { + schain = currentBidRequest.schain; + } + + return this.serializeSupplyChain(schain) + } + const constructGlobalParams = currentBidRequest => { const sdkVersion = getSDKVersion(currentBidRequest); const prebidVersion = getGlobal().version; @@ -66,7 +79,7 @@ export const spec = { resp: 'vast4', prof: currentBidRequest.params.profile, csid: currentBidRequest.params.siteSectionId, - caid: currentBidRequest.params.videoAssetId, + caid: currentBidRequest.params.videoAssetId ? currentBidRequest.params.videoAssetId : 0, pvrn: getRandomNumber(), vprn: getRandomNumber(), flag: setFlagParameter(currentBidRequest.params.flags), @@ -128,23 +141,6 @@ export const spec = { } } - // Add schain object - let schain = deepAccess(bidderRequest, 'ortb2.source.schain'); - if (!schain) { - schain = deepAccess(bidderRequest, 'ortb2.source.ext.schain'); - } - if (!schain) { - schain = currentBidRequest.schain; - } - - if (schain) { - try { - keyValues.schain = JSON.stringify(schain); - } catch (error) { - logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error); - } - } - // Add 3rd party user ID if (currentBidRequest.userIdAsEids && currentBidRequest.userIdAsEids.length > 0) { try { @@ -261,9 +257,10 @@ export const spec = { return slotParams } - const constructDataString = (globalParams, keyValues, slotParams) => { + const constructDataString = (globalParams, keyValues, serializedSChain, slotParams) => { const globalParamsString = appendParams(globalParams) + ';'; - const keyValuesString = appendParams(keyValues) + ';'; + // serializedSChain requires special encoding logic as outlined in the ORTB spec https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/supplychainobject.md + const keyValuesString = appendParams(keyValues) + serializedSChain + ';'; const slotParamsString = appendParams(slotParams) + ';'; return globalParamsString + keyValuesString + slotParamsString; @@ -274,6 +271,21 @@ export const spec = { }); }, + /** + * Serialize a supply chain object to a string uri encoded + * + * @param {*} schain object + */ + serializeSupplyChain: function(schain) { + if (!schain || !schain.nodes) return ''; + const nodesProperties = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + return `&schain=${schain.ver},${schain.complete}!` + + schain.nodes.map(node => nodesProperties.map(prop => + node[prop] ? encodeURIComponent(node[prop]) : '') + .join(',')) + .join('!'); + }, + /** * Unpack the response from the server into a list of bids. * diff --git a/modules/fwsspBidAdapter.md b/modules/fwsspBidAdapter.md index 498897b676a..fb44dd279b2 100644 --- a/modules/fwsspBidAdapter.md +++ b/modules/fwsspBidAdapter.md @@ -8,6 +8,37 @@ Maintainer: vis@freewheel.com Module that connects to Freewheel MRM's demand sources +# Basic Test Request +``` +const adUnits = [{ + code: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480], + minduration: 30, + maxduration: 60 + } + }, + bids: [{ + bidder: 'fwssp', // or use alias 'freewheel-mrm' + params: { + serverUrl: 'https://example.com/ad/g/1', + networkId: '42015', + profile: '42015:js_allinone_profile', + siteSectionId: 'js_allinone_demo_site_section', + videoAssetId: '1', // optional: default value of 0 will used if not included + flags: '+play-uapl' // optional: users may include capability if needed + mode: 'live', + adRequestKeyValues: { // optional: users may include adRequestKeyValues if needed + _fw_player_width: '1920', + _fw_player_height: '1080' + }, + format: 'inbanner' + } + }] +}]; +``` + # Example Inbanner Ad Request ``` { @@ -19,6 +50,17 @@ Module that connects to Freewheel MRM's demand sources }, bids: [{ bidder: 'fwssp', + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + }] + }, params: { bidfloor: 2.00, serverUrl: 'https://example.com/ad/g/1', @@ -26,7 +68,7 @@ Module that connects to Freewheel MRM's demand sources profile: '42015:js_allinone_profile', siteSectionId: 'js_allinone_demo_site_section', flags: '+play', - videoAssetId: '0', + videoAssetId: '1`, // optional: default value of 0 will used if not included timePosition: 120, adRequestKeyValues: { _fw_player_width: '1920', @@ -51,6 +93,17 @@ Module that connects to Freewheel MRM's demand sources }, bids: [{ bidder: 'fwssp', + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + }] + }, params: { bidfloor: 2.00, serverUrl: 'https://example.com/ad/g/1', @@ -58,7 +111,7 @@ Module that connects to Freewheel MRM's demand sources profile: '42015:js_allinone_profile', siteSectionId: 'js_allinone_demo_site_section', flags: '+play', - videoAssetId: '0', + videoAssetId: '1', // optional: default value of 0 will used if not included mode: 'live', timePosition: 120, tpos: 300, diff --git a/test/spec/modules/fwsspBidAdapter_spec.js b/test/spec/modules/fwsspBidAdapter_spec.js index 819167587ae..ee7e762c18c 100644 --- a/test/spec/modules/fwsspBidAdapter_spec.js +++ b/test/spec/modules/fwsspBidAdapter_spec.js @@ -10,7 +10,6 @@ describe('fwsspBidAdapter', () => { networkId: '42015', profile: '42015:js_allinone_profile', siteSectionId: 'js_allinone_demo_site_section', - videoAssetId: '0' } }; expect(spec.isBidRequestValid(bid)).to.be.true; @@ -22,7 +21,6 @@ describe('fwsspBidAdapter', () => { networkId: '42015', profile: '42015:js_allinone_profile', siteSectionId: 'js_allinone_demo_site_section', - videoAssetId: '0' } }; expect(spec.isBidRequestValid(bid)).to.be.false; @@ -34,7 +32,6 @@ describe('fwsspBidAdapter', () => { serverUrl: 'https://example.com/ad/g/1', profile: '42015:js_allinone_profile', siteSectionId: 'js_allinone_demo_site_section', - videoAssetId: '0' } }; expect(spec.isBidRequestValid(bid)).to.be.false; @@ -46,7 +43,6 @@ describe('fwsspBidAdapter', () => { serverUrl: 'https://example.com/ad/g/1', networkId: '42015', siteSectionId: 'js_allinone_demo_site_section', - videoAssetId: '0' } }; expect(spec.isBidRequestValid(bid)).to.be.false; @@ -58,19 +54,6 @@ describe('fwsspBidAdapter', () => { serverUrl: 'https://example.com/ad/g/1', networkId: '42015', profile: '42015:js_allinone_profile', - videoAssetId: '0' - } - }; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - - it('should return false when videoAssetId is missing', () => { - const bid = { - params: { - serverUrl: 'https://example.com/ad/g/1', - networkId: '42015', - profile: '42015:js_allinone_profile', - siteSectionId: 'js_allinone_demo_site_section' } }; expect(spec.isBidRequestValid(bid)).to.be.false; @@ -112,7 +95,6 @@ describe('fwsspBidAdapter', () => { 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', 'flags': '+play', - 'videoAssetId': '0', 'timePosition': 120, 'adRequestKeyValues': { '_fw_player_width': '1920', @@ -137,7 +119,7 @@ describe('fwsspBidAdapter', () => { } }; - it('should build a valid server request', () => { + it('should build a valid server request with default caid of 0', () => { const requests = spec.buildRequests(getBidRequests(), bidderRequest); expect(requests).to.be.an('array').that.is.not.empty; const request = requests[0]; @@ -169,7 +151,7 @@ describe('fwsspBidAdapter', () => { expect(actualDataString).to.not.include('mind'); expect(actualDataString).to.not.include('maxd;'); // schain check - const expectedEncodedSchainString = encodeURIComponent('{"ver":"1.0","complete":1,"nodes":[{"asi":"example.com","sid":"0","hp":1,"rid":"bidrequestid","domain":"example.com"}]}'); + const expectedEncodedSchainString = '1.0,1!example.com,0,1,bidrequestid,,example.com'; expect(actualDataString).to.include(expectedEncodedSchainString); }); @@ -177,13 +159,23 @@ describe('fwsspBidAdapter', () => { const requests = spec.buildRequests(getBidRequests(), bidderRequest); expect(requests).to.be.an('array').that.is.not.empty; const request = requests[0]; - const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=EUR&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`; + const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=EUR&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=1.0,1!example.com,0,1,bidrequestid,,example.com;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`; const actualUrl = `${request.url}?${request.data}`; // Remove pvrn and vprn from both URLs before comparing const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, ''); expect(cleanUrl(actualUrl)).to.equal(cleanUrl(expectedUrl)); }); + it('should use params.videoAssetId as caid', () => { + const bidRequests = getBidRequests(); + bidRequests[0].params.videoAssetId = 10; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.be.an('array').that.is.not.empty; + const request = requests[0]; + const actualDataString = request.data; + expect(actualDataString).to.include('caid=10'); + }); + it('should return the correct width and height when _fw_player_width and _fw_player_height are not present in adRequestKeyValues', () => { const bidRequests = [{ 'bidder': 'fwssp', @@ -243,7 +235,7 @@ describe('fwsspBidAdapter', () => { bidRequests[0].params.adRequestKeyValues._fw_is_lat = 1; const requests = spec.buildRequests(bidRequests, bidderRequest); const request = requests[0]; - const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_coppa=1&_fw_atts=1&_fw_is_lat=1&_fw_bidfloor=2&_fw_bidfloorcur=EUR&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`; + const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_coppa=1&_fw_atts=1&_fw_is_lat=1&_fw_bidfloor=2&_fw_bidfloorcur=EUR&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=1.0,1!example.com,0,1,bidrequestid,,example.com;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`; const actualUrl = `${request.url}?${request.data}`; // Remove pvrn and vprn from both URLs before comparing const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, ''); @@ -276,7 +268,7 @@ describe('fwsspBidAdapter', () => { const requests = spec.buildRequests(bidRequests, bidderRequest2); const request = requests[0]; - const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_coppa=0&_fw_atts=0&_fw_is_lat=0&_fw_bidfloor=2&_fw_bidfloorcur=EUR&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`; + const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_coppa=0&_fw_atts=0&_fw_is_lat=0&_fw_bidfloor=2&_fw_bidfloorcur=EUR&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=1.0,1!example.com,0,1,bidrequestid,,example.com;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`; const actualUrl = `${request.url}?${request.data}`; // Remove pvrn and vprn from both URLs before comparing const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, ''); @@ -331,7 +323,7 @@ describe('fwsspBidAdapter', () => { const request = requests[0]; // schain check - const expectedEncodedSchainString = encodeURIComponent(JSON.stringify(schain1)); + const expectedEncodedSchainString = '1.0,1!test1.com,0,1,bidrequestid1,,test1.com'; expect(request.data).to.include(expectedEncodedSchainString); }); @@ -362,7 +354,7 @@ describe('fwsspBidAdapter', () => { const request = requests[0]; // schain check - const expectedEncodedSchainString = encodeURIComponent(JSON.stringify(schain2)); + const expectedEncodedSchainString = '1.0,1!test2.com,0,2,bidrequestid2,,test2.com'; expect(request.data).to.include(expectedEncodedSchainString); }); }); @@ -401,7 +393,6 @@ describe('fwsspBidAdapter', () => { 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', 'flags': '+play', - 'videoAssetId': '0', 'mode': 'live', 'timePosition': 120, 'tpos': 300, @@ -446,9 +437,9 @@ describe('fwsspBidAdapter', () => { it('should return context and placement with default values', () => { const request = spec.buildRequests(getBidRequests()); const payload = request[0].data; - expect(payload).to.include('_fw_video_context=&'); ; + expect(payload).to.include('_fw_video_context=&'); expect(payload).to.include('_fw_placement_type=null&'); - expect(payload).to.include('_fw_plcmt_type=null;'); + expect(payload).to.include('_fw_plcmt_type=null&'); }); it('should assign placement and context when format is inbanner', () => { @@ -457,9 +448,9 @@ describe('fwsspBidAdapter', () => { bidRequest.mediaTypes.video.plcmt = 'test-plcmt-type'; const request = spec.buildRequests([bidRequest]); const payload = request[0].data; - expect(payload).to.include('_fw_video_context=In-Banner&'); ; + expect(payload).to.include('_fw_video_context=In-Banner&'); expect(payload).to.include('_fw_placement_type=2&'); - expect(payload).to.include('_fw_plcmt_type=test-plcmt-type;'); + expect(payload).to.include('_fw_plcmt_type=test-plcmt-type&'); }); it('should build a valid server request', () => { @@ -497,7 +488,7 @@ describe('fwsspBidAdapter', () => { expect(actualDataString).to.include('mind=30'); expect(actualDataString).to.include('maxd=60;'); // schain check - const expectedEncodedSchainString = encodeURIComponent('{"ver":"1.0","complete":1,"nodes":[{"asi":"example.com","sid":"0","hp":1,"rid":"bidrequestid","domain":"example.com"}]}'); + const expectedEncodedSchainString = '1.0,1!example.com,0,1,bidrequestid,,example.com'; expect(actualDataString).to.include(expectedEncodedSchainString); }); @@ -506,7 +497,7 @@ describe('fwsspBidAdapter', () => { expect(requests).to.be.an('array').that.is.not.empty; const request = requests[0]; - const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_gdpr_consented_providers=test_providers&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D&loc=http%3A%2F%2Fwww.test.com&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`; + const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_gdpr_consented_providers=test_providers&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&loc=http%3A%2F%2Fwww.test.com&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null&schain=1.0,1!example.com,0,1,bidrequestid,,example.com;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`; const actualUrl = `${request.url}?${request.data}`; // Remove pvrn and vprn from both URLs before comparing const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, ''); @@ -532,7 +523,7 @@ describe('fwsspBidAdapter', () => { const requests = spec.buildRequests(getBidRequests(), bidderRequest2); expect(requests).to.be.an('array').that.is.not.empty; const request = requests[0]; - const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consented_providers=test_providers&gpp=test_ortb2_gpp&gpp_sid=test_ortb2_gpp_sid&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`; + const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consented_providers=test_providers&gpp=test_ortb2_gpp&gpp_sid=test_ortb2_gpp_sid&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null&schain=1.0,1!example.com,0,1,bidrequestid,,example.com;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`; const actualUrl = `${request.url}?${request.data}`; // Remove pvrn and vprn from both URLs before comparing const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, ''); @@ -595,7 +586,6 @@ describe('fwsspBidAdapter', () => { 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', 'flags': '+play', - 'videoAssetId': '0', 'mode': 'live', 'vclr': 'js-7.11.0-prebid-', 'timePosition': 120, @@ -1157,7 +1147,6 @@ describe('fwsspBidAdapter', () => { 'networkId': '42015', 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', - 'videoAssetId': '0', 'bidfloor': 1.25, 'bidfloorcur': 'GBP' } @@ -1184,7 +1173,6 @@ describe('fwsspBidAdapter', () => { 'networkId': '42015', 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', - 'videoAssetId': '0', 'bidfloor': 1.0, 'bidfloorcur': 'USD' }, @@ -1215,7 +1203,6 @@ describe('fwsspBidAdapter', () => { 'networkId': '42015', 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', - 'videoAssetId': '0' }, getFloor: () => ({ floor: 0, @@ -1244,7 +1231,6 @@ describe('fwsspBidAdapter', () => { 'networkId': '42015', 'profile': '42015:js_allinone_profile', 'siteSectionId': 'js_allinone_demo_site_section', - 'videoAssetId': '0' } }]; @@ -1254,4 +1240,109 @@ describe('fwsspBidAdapter', () => { expect(payload).to.include('_fw_bidfloorcur=USD'); }); }); + + describe('schain tests', () => { + const getBidRequests = () => { + return [{ + 'bidder': 'fwssp', + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 600], + 'minduration': 30, + 'maxduration': 60, + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'params': { + 'bidfloor': 2.00, + 'serverUrl': 'https://example.com/ad/g/1', + 'networkId': '42015', + 'profile': '42015:js_allinone_profile', + 'siteSectionId': 'js_allinone_demo_site_section', + 'flags': '+play', + 'videoAssetId': '0', + 'mode': 'live', + 'timePosition': 120, + 'tpos': 300, + 'slid': 'Midroll', + 'slau': 'midroll', + 'adRequestKeyValues': { + '_fw_player_width': '1920', + '_fw_player_height': '1080' + }, + 'gdpr_consented_providers': 'test_providers' + } + }] + }; + + const bidderRequest = { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true + }, + uspConsent: 'uspConsentString', + gppConsent: { + gppString: 'gppString', + applicableSections: [8] + }, + ortb2: { + regs: { + gpp: 'test_ortb2_gpp', + gpp_sid: 'test_ortb2_gpp_sid' + }, + site: { + content: { + id: 'test_content_id', + title: 'test_content_title' + } + } + }, + refererInfo: { + page: 'http://www.test.com' + } + }; + + it('should not include schain in the adrequest URL if schain is missing from bidrequest', () => { + const requests = spec.buildRequests(getBidRequests(), bidderRequest); + expect(requests).to.be.an('array').that.is.not.empty; + const request = requests[0]; + const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.11.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_gdpr_consented_providers=test_providers&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&loc=http%3A%2F%2Fwww.test.com&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`; + const actualUrl = `${request.url}?${request.data}`; + // Remove pvrn and vprn from both URLs before comparing + const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, ''); + expect(cleanUrl(actualUrl)).to.equal(cleanUrl(expectedUrl)); + }); + + it('should only encode comma within attribute value', () => { + const bidRequests = getBidRequests(); + const bidderRequest2 = { ...bidderRequest } + const schain1 = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'test1.com', + sid: '123,B', + hp: 1, + rid: 'bidrequestid1', + domain: 'test1.com' + }] + }; + + bidderRequest2.ortb2 = { + source: { + schain: schain1, + } + }; + const requests = spec.buildRequests(bidRequests, bidderRequest2); + const request = requests[0]; + + // schain check + const expectedEncodedSchainString = '1.0,1!test1.com,123%2CB,1,bidrequestid1,,test1.com'; + expect(request.data).to.include(expectedEncodedSchainString); + }); + }); }); From 2f0a8a9433505276593066903e45b44961effe77 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 18 Nov 2025 16:48:11 -0500 Subject: [PATCH 119/147] Remove security-updates-only from dependabot config invalid property --- .github/dependabot.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 80e3ea0be72..007ba6d26b4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,7 +28,6 @@ updates: target-branch: "master" schedule: interval: "daily" - security-updates-only: true open-pull-requests-limit: 0 groups: all-security: From d632c95b1c45f5bac5e10e618d09336673f1cc70 Mon Sep 17 00:00:00 2001 From: gregneuwo Date: Tue, 18 Nov 2025 22:57:51 +0100 Subject: [PATCH 120/147] Neuwo Rtd Module: Add url cleaning feature to Neuwo RTD module (#14089) * feat: add url cleaning functionality Add configurable URL cleaning options to strip query parameters and fragments before sending URLs to Neuwo API: - stripAllQueryParams: removes all query parameters - stripQueryParamsForDomains: removes all params for specific domains/subdomains - stripQueryParams: removes specific named parameters - stripFragments: removes URL hash fragments * test: add tests for url cleaning functionality Add test cases for cleanUrl function covering: - Query parameter stripping (all, domain-specific, selective) - Fragment stripping and combinations - Edge cases (malformed URLs, encoding, delimiters) - Domain/subdomain matching logic - Option priority and fallthrough behavior - Integration tests with getBidRequestData * docs: update documentation for url cleaning Update module documentation to include: - URL cleaning configuration options and examples - Parameter table with stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, stripFragments - `npm ci` as dependencies installation command - Linting section with eslint command - Adjust commands examples - Update test commands to use test-only and test-only-nobuild - Update example page to include URL cleaning functionality - Unified commands between the example file and the module readme - Improve input structure on the example page - Enable saving input values on the example page to facilitate manual testing - Update contact information * Neuwo RTD Module feat: add API response caching - Added `enableCache` parameter (default: `true`) to cache API responses and avoid redundant requests during the page session - Implemented `clearCache()` function for testing purposes - Updated *modules/neuwoRtdProvider.js* to store responses in `globalCachedResponse` and reuse them when caching is enabled - Added cache control UI to *integrationExamples/gpt/neuwoRtdProvider_example.html* with checkbox and state persistence - Updated *modules/neuwoRtdProvider.md* documentation with caching configuration details - Added test coverage in *test/spec/modules/neuwoRtdProvider_spec.js* for both enabled and disabled cache scenarios --------- Co-authored-by: grzgm <125459798+grzgm@users.noreply.github.com> --- .../gpt/neuwoRtdProvider_example.html | 161 +++- modules/neuwoRtdProvider.js | 153 +++- modules/neuwoRtdProvider.md | 117 ++- test/spec/modules/neuwoRtdProvider_spec.js | 717 +++++++++++++++++- 4 files changed, 1111 insertions(+), 37 deletions(-) diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index 33cdccd0e69..3d6fef98995 100644 --- a/integrationExamples/gpt/neuwoRtdProvider_example.html +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -121,10 +121,30 @@ const inputIabContentTaxonomyVersion = document.getElementById('iab-content-taxonomy-version'); const iabContentTaxonomyVersion = inputIabContentTaxonomyVersion ? inputIabContentTaxonomyVersion.value : undefined; + // Cache Option + const inputEnableCache = document.getElementById('enable-cache'); + const enableCache = inputEnableCache ? inputEnableCache.checked : undefined; + + // URL Stripping Options + 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 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 stripFragments = inputStripFragments ? inputStripFragments.checked : undefined; + pbjs.que.push(function () { pbjs.setConfig({ debug: true, realTimeData: { + auctionDelay: 500, dataProviders: [ { name: "NeuwoRTDModule", @@ -133,7 +153,12 @@ neuwoApiUrl, neuwoApiToken, websiteToAnalyseUrl, - iabContentTaxonomyVersion + iabContentTaxonomyVersion, + enableCache, + stripAllQueryParams, + stripQueryParamsForDomains, + stripQueryParams, + stripFragments } } ] @@ -166,11 +191,14 @@

Basic Prebid.js Example using Neuwo Rtd Provider

after running commands in the prebid.js source folder that includes libraries/modules/neuwoRtdProvider.js + // Install dependencies npm ci + + // Run a local development server npx gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter // No tests - npx gulp serve-fast --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --notests + npx gulp serve-fast --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter // Only tests npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/neuwoRtdProvider_spec.js @@ -180,10 +208,49 @@

Basic Prebid.js Example using Neuwo Rtd Provider

Neuwo Rtd Provider Configuration

Add token and url to use for Neuwo extension configuration

- - - - +
+ +
+
+ +
+
+ +
+ +

IAB Content Taxonomy Options

+
+ +
+ +

Cache Options

+
+ +
+ +

URL Cleaning Options

+
+ +
+
+ +
+
+ +
+
+ +
+
@@ -232,4 +299,86 @@

Neuwo Data in Bid Request

if (helper) helper.style.display = location.href !== 'http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html' ? 'block' : 'none'; + + \ No newline at end of file diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 99715f0b484..3255547aa47 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -23,6 +23,17 @@ import { deepSetValue, logError, logInfo, mergeDeep } from "../src/utils.js"; const MODULE_NAME = "NeuwoRTDModule"; export const DATA_PROVIDER = "www.neuwo.ai"; +// Cached API response to avoid redundant requests. +let globalCachedResponse; + +/** + * Clears the cached API response. Primarily used for testing. + * @private + */ +export function clearCache() { + globalCachedResponse = undefined; +} + // Maps the IAB Content Taxonomy version string to the corresponding segtax ID. // Based on https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/main/AdCOM%20v1.0%20FINAL.md#list--category-taxonomies- const IAB_CONTENT_TAXONOMY_MAP = { @@ -36,11 +47,13 @@ const IAB_CONTENT_TAXONOMY_MAP = { /** * Validates the configuration and initialises the module. + * * @param {Object} config The module configuration. * @param {Object} userConsent The user consent object. * @returns {boolean} `true` if the module is configured correctly, otherwise `false`. */ function init(config, userConsent) { + logInfo(MODULE_NAME, "init:", config, userConsent); const params = config?.params || {}; if (!params.neuwoApiUrl) { logError(MODULE_NAME, "init:", "Missing Neuwo Edge API Endpoint URL"); @@ -55,18 +68,46 @@ function init(config, userConsent) { /** * Fetches contextual data from the Neuwo API and enriches the bid request object with IAB categories. + * Uses cached response if available to avoid redundant API calls. + * * @param {Object} reqBidsConfigObj The bid request configuration object. * @param {function} callback The callback function to continue the auction. * @param {Object} config The module configuration. + * @param {Object} config.params Configuration parameters. + * @param {string} config.params.neuwoApiUrl The Neuwo API endpoint URL. + * @param {string} config.params.neuwoApiToken The Neuwo API authentication token. + * @param {string} [config.params.websiteToAnalyseUrl] Optional URL to analyze instead of current page. + * @param {string} [config.params.iabContentTaxonomyVersion] IAB content taxonomy version (default: "3.0"). + * @param {boolean} [config.params.enableCache=true] If true, caches API responses to avoid redundant requests (default: true). + * @param {boolean} [config.params.stripAllQueryParams] If true, strips all query parameters from the URL. + * @param {string[]} [config.params.stripQueryParamsForDomains] List of domains for which to strip all query params. + * @param {string[]} [config.params.stripQueryParams] List of specific query parameter names to strip. + * @param {boolean} [config.params.stripFragments] If true, strips URL fragments (hash). * @param {Object} userConsent The user consent object. */ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { logInfo(MODULE_NAME, "getBidRequestData:", "starting getBidRequestData", config); - const { websiteToAnalyseUrl, neuwoApiUrl, neuwoApiToken, iabContentTaxonomyVersion } = - config.params; + const { + websiteToAnalyseUrl, + neuwoApiUrl, + neuwoApiToken, + iabContentTaxonomyVersion, + enableCache = true, + stripAllQueryParams, + stripQueryParamsForDomains, + stripQueryParams, + stripFragments, + } = config.params; - const pageUrl = encodeURIComponent(websiteToAnalyseUrl || getRefererInfo().page); + const rawUrl = websiteToAnalyseUrl || getRefererInfo().page; + const processedUrl = cleanUrl(rawUrl, { + stripAllQueryParams, + stripQueryParamsForDomains, + stripQueryParams, + stripFragments + }); + const pageUrl = encodeURIComponent(processedUrl); // Adjusted for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') const joiner = neuwoApiUrl.indexOf("?") < 0 ? "?" : "&"; const neuwoApiUrlFull = @@ -75,8 +116,13 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen const success = (response) => { logInfo(MODULE_NAME, "getBidRequestData:", "Neuwo API raw response:", response); try { - const responseJson = JSON.parse(response); - injectIabCategories(responseJson, reqBidsConfigObj, iabContentTaxonomyVersion); + const responseParsed = JSON.parse(response); + + if (enableCache) { + globalCachedResponse = responseParsed; + } + + injectIabCategories(responseParsed, reqBidsConfigObj, iabContentTaxonomyVersion); } catch (ex) { logError(MODULE_NAME, "getBidRequestData:", "Error while processing Neuwo API response", ex); } @@ -88,15 +134,102 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen callback(); }; - ajax(neuwoApiUrlFull, { success, error }, null); + if (enableCache && globalCachedResponse) { + logInfo(MODULE_NAME, "getBidRequestData:", "Using cached response:", globalCachedResponse); + injectIabCategories(globalCachedResponse, reqBidsConfigObj, iabContentTaxonomyVersion); + callback(); + } else { + logInfo(MODULE_NAME, "getBidRequestData:", "Calling Neuwo API Endpoint: ", neuwoApiUrlFull); + ajax(neuwoApiUrlFull, { success, error }, null); + } } // // HELPER FUNCTIONS // +/** + * Cleans a URL by stripping query parameters and/or fragments based on the provided configuration. + * + * @param {string} url The URL to clean. + * @param {Object} options Cleaning options. + * @param {boolean} [options.stripAllQueryParams] If true, strips all query parameters. + * @param {string[]} [options.stripQueryParamsForDomains] List of domains for which to strip all query params. + * @param {string[]} [options.stripQueryParams] List of specific query parameter names to strip. + * @param {boolean} [options.stripFragments] If true, strips URL fragments (hash). + * @returns {string} The cleaned URL. + */ +export function cleanUrl(url, options = {}) { + const { stripAllQueryParams, stripQueryParamsForDomains, stripQueryParams, stripFragments } = options; + + if (!url) { + logInfo(MODULE_NAME, "cleanUrl:", "Empty or null URL provided, returning as-is"); + return url; + } + + logInfo(MODULE_NAME, "cleanUrl:", "Input URL:", url, "Options:", options); + + try { + const urlObj = new URL(url); + + // Strip fragments if requested + if (stripFragments === true) { + urlObj.hash = ""; + logInfo(MODULE_NAME, "cleanUrl:", "Stripped fragment from URL"); + } + + // Option 1: Strip all query params unconditionally + if (stripAllQueryParams === true) { + urlObj.search = ""; + const cleanedUrl = urlObj.toString(); + logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", cleanedUrl); + return cleanedUrl; + } + + // Option 2: Strip all query params for specific domains + if (Array.isArray(stripQueryParamsForDomains) && stripQueryParamsForDomains.length > 0) { + const hostname = urlObj.hostname; + const shouldStripForDomain = stripQueryParamsForDomains.some(domain => { + // Support exact match or subdomain match + return hostname === domain || hostname.endsWith("." + domain); + }); + + if (shouldStripForDomain) { + urlObj.search = ""; + const cleanedUrl = urlObj.toString(); + logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", cleanedUrl); + return cleanedUrl; + } + } + + // Option 3: Strip specific query parameters + // Caveats: + // - "?=value" is treated as query parameter with key "" and value "value" + // - "??" is treated as query parameter with key "?" and value "" + if (Array.isArray(stripQueryParams) && stripQueryParams.length > 0) { + const queryParams = urlObj.searchParams; + logInfo(MODULE_NAME, "cleanUrl:", `Query parameters to strip: ${stripQueryParams}`); + stripQueryParams.forEach(param => { + queryParams.delete(param); + }); + urlObj.search = queryParams.toString(); + const cleanedUrl = urlObj.toString(); + logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", cleanedUrl); + return cleanedUrl; + } + + const finalUrl = urlObj.toString(); + logInfo(MODULE_NAME, "cleanUrl:", "Output URL:", finalUrl); + return finalUrl; + } catch (e) { + logError(MODULE_NAME, "cleanUrl:", "Error cleaning URL:", e); + return url; + } +} + /** * Injects data into the OpenRTB 2.x global fragments of the bid request object. + * * @param {Object} reqBidsConfigObj The main bid request configuration object. * @param {string} path The dot-notation path where the data should be injected (e.g., 'site.content.data'). * @param {*} data The data to inject at the specified path. @@ -109,6 +242,7 @@ export function injectOrtbData(reqBidsConfigObj, path, data) { /** * Builds an IAB category data object for use in OpenRTB. + * * @param {Object} marketingCategories Marketing Categories returned by Neuwo API. * @param {string[]} tiers The tier keys to extract from marketingCategories. * @param {number} segtax The IAB taxonomy version Id. @@ -141,12 +275,13 @@ export function buildIabData(marketingCategories, tiers, segtax) { /** * Processes the Neuwo API response to build and inject IAB content and audience categories * into the bid request object. - * @param {Object} responseJson The parsed JSON response from the Neuwo API. + * + * @param {Object} responseParsed The parsed JSON response from the Neuwo API. * @param {Object} reqBidsConfigObj The bid request configuration object to be modified. * @param {string} iabContentTaxonomyVersion The version of the IAB content taxonomy to use for segtax mapping. */ -function injectIabCategories(responseJson, reqBidsConfigObj, iabContentTaxonomyVersion) { - const marketingCategories = responseJson.marketing_categories; +function injectIabCategories(responseParsed, reqBidsConfigObj, iabContentTaxonomyVersion) { + const marketingCategories = responseParsed.marketing_categories; if (!marketingCategories) { logError(MODULE_NAME, "injectIabCategories:", "No Marketing Categories in Neuwo API response."); diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md index acd3f27d3ff..804130be1e6 100644 --- a/modules/neuwoRtdProvider.md +++ b/modules/neuwoRtdProvider.md @@ -63,41 +63,98 @@ ortb2: { } ``` -To get started, you can generate your API token at [https://neuwo.ai/generatetoken/](https://neuwo.ai/generatetoken/) or [contact us here](https://neuwo.ai/contact-us/). +To get started, you can generate your API token at [https://neuwo.ai/generatetoken/](https://neuwo.ai/generatetoken/), send us an email to [neuwo-helpdesk@neuwo.ai](mailto:neuwo-helpdesk@neuwo.ai) or [contact us here](https://neuwo.ai/contact-us/). ## Configuration -> **Important:** You must add the domain (origin) where Prebid.js is running to the list of allowed origins in Neuwo Edge API configuration. If you have problems, [contact us here](https://neuwo.ai/contact-us/). +> **Important:** You must add the domain (origin) where Prebid.js is running to the list of allowed origins in Neuwo Edge API configuration. If you have problems, send us an email to [neuwo-helpdesk@neuwo.ai](mailto:neuwo-helpdesk@neuwo.ai) or [contact us here](https://neuwo.ai/contact-us/). This module is configured as part of the `realTimeData.dataProviders` object. ```javascript pbjs.setConfig({ realTimeData: { - dataProviders: [{ - name: 'NeuwoRTDModule', - params: { - neuwoApiUrl: '', - neuwoApiToken: '', - iabContentTaxonomyVersion: '3.0', - } - }] - } + auctionDelay: 500, // Value can be adjusted based on the needs + dataProviders: [ + { + name: "NeuwoRTDModule", + waitForIt: true, + params: { + neuwoApiUrl: "", + neuwoApiToken: "", + iabContentTaxonomyVersion: "3.0", + enableCache: true, // Default: true. Caches API responses to avoid redundant requests + }, + }, + ], + }, }); ``` **Parameters** -| Name | Type | Required | Default | Description | -| :--------------------------------- | :----- | :------- | :------ | :------------------------------------------------------------------------------------------------ | -| `name` | String | Yes | | The name of the module, which is `NeuwoRTDModule`. | -| `params` | Object | Yes | | Container for module-specific parameters. | -| `params.neuwoApiUrl` | String | Yes | | The endpoint URL for the Neuwo Edge API. | -| `params.neuwoApiToken` | String | Yes | | Your unique API token provided by Neuwo. | -| `params.iabContentTaxonomyVersion` | String | No | `'3.0'` | Specifies the version of the IAB Content Taxonomy to be used. Supported values: `'2.2'`, `'3.0'`. | +| Name | Type | Required | Default | Description | +| :---------------------------------- | :------- | :------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | String | Yes | | The name of the module, which is `NeuwoRTDModule`. | +| `params` | Object | Yes | | Container for module-specific parameters. | +| `params.neuwoApiUrl` | String | Yes | | The endpoint URL for the Neuwo Edge API. | +| `params.neuwoApiToken` | String | Yes | | Your unique API token provided by Neuwo. | +| `params.iabContentTaxonomyVersion` | String | No | `'3.0'` | Specifies the version of the IAB Content Taxonomy to be used. Supported values: `'2.2'`, `'3.0'`. | +| `params.enableCache` | Boolean | No | `true` | If `true`, caches API responses to avoid redundant requests for the same page during the session. Set to `false` to disable caching and make a fresh API call on every bid request. | +| `params.stripAllQueryParams` | Boolean | No | `false` | If `true`, strips all query parameters from the URL before analysis. Takes precedence over other stripping options. | +| `params.stripQueryParamsForDomains` | String[] | No | `[]` | List of domains for which to strip **all** query parameters. When a domain matches, all query params are removed for that domain and all its subdomains (e.g., `'example.com'` strips params for both `'example.com'` and `'sub.example.com'`). This option takes precedence over `stripQueryParams` for matching domains. | +| `params.stripQueryParams` | String[] | No | `[]` | List of specific query parameter names to strip from the URL (e.g., `['utm_source', 'fbclid']`). Other parameters are preserved. Only applies when the domain does not match `stripQueryParamsForDomains`. | +| `params.stripFragments` | Boolean | No | `false` | If `true`, strips URL fragments (hash, e.g., `#section`) from the URL before analysis. | + +### API Response Caching + +By default, the module caches API responses during the page session to optimise performance and reduce redundant API calls. This behaviour can be disabled by setting `enableCache: false` if needed for dynamic content scenarios. + +### URL Cleaning Options + +The module provides optional URL cleaning capabilities to strip query parameters and/or fragments from the analysed URL before sending it to the Neuwo API. This can be useful for privacy, caching, or analytics purposes. + +**Example with URL cleaning:** + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 500, // Value can be adjusted based on the needs + dataProviders: [ + { + name: "NeuwoRTDModule", + waitForIt: true, + params: { + neuwoApiUrl: "", + neuwoApiToken: "", + iabContentTaxonomyVersion: "3.0", + + // Option 1: Strip all query parameters from the URL + stripAllQueryParams: true, + + // Option 2: Strip all query parameters only for specific domains + // stripQueryParamsForDomains: ['example.com', 'another-domain.com'], + + // Option 3: Strip specific query parameters by name + // stripQueryParams: ['utm_source', 'utm_campaign', 'fbclid'], + + // Optional: Strip URL fragments (hash) + stripFragments: true, + }, + }, + ], + }, +}); +``` ## Local Development +Install the exact versions of packages specified in the lockfile: + +```bash +npm ci +``` + > **Linux** Linux might require exporting the following environment variable before running the commands below: > `export CHROME_BIN=/usr/bin/chromium` @@ -110,20 +167,38 @@ npx gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter For a faster build without tests: ```bash -npx gulp serve-fast --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --notests +npx gulp serve-fast --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter ``` After starting the server, you can access the example page at: [http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html](http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html) ### Add development tools if necessary + If you don't have gulp-cli installed globally, run the following command in your Prebid.js source folder: + ```bash npm i -g gulp-cli ``` +## Linting + +To lint the module: + +```bash +npx eslint 'modules/neuwoRtdProvider.js' --cache --cache-strategy content +``` + ## Testing + To run the module-specific tests: + +```bash +npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/euwoRtdProvider_spec.js +``` + +Skip building, if the project has already been built: + ```bash -npx gulp test-only --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter --file=test/spec/modules/neuwoRtdProvider_spec.js -``` \ No newline at end of file +npx gulp test-only-nobuild --file=test/spec/modules/neuwoRtdProvider_spec.js +``` diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index 1f75b1441e8..4b2951afe33 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -82,6 +82,11 @@ function bidsConfiglike() { } describe("neuwoRtdModule", function () { + beforeEach(function () { + // Clear the global cache before each test to ensure test isolation + neuwo.clearCache(); + }); + describe("init", function () { it("should return true when all required parameters are provided", function () { expect( @@ -513,7 +518,440 @@ describe("neuwoRtdModule", function () { }); }); - // NEW TESTS START HERE + describe("cleanUrl", function () { + describe("when no stripping options are provided", function () { + it("should return the URL unchanged", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const result = neuwo.cleanUrl(url, {}); + expect(result, "should return the original URL with all query params intact").to.equal(url); + }); + + it("should return the URL unchanged when options object is empty", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url); + expect(result, "should handle missing options parameter").to.equal(url); + }); + }); + + describe("with query parameters edge cases", function () { + it("should strip all query parameters from the URL for `stripAllQueryParams` (edge cases)", function () { + const stripAll = (url) => neuwo.cleanUrl(url, { stripAllQueryParams: true }); + const expected = "https://example.com/page"; + const expectedWithFragment = "https://example.com/page#anchor"; + + // Basic formats + expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); + expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); + expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); + expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + + // Multiple parameters + expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); + expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + + // Special characters and encoding + expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); + expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); + expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + + // Delimiters and syntax edge cases + expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); + expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + + // Empty and missing cases + expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); + expect(stripAll("https://example.com/page??"), "should remove double question mark").to.equal(expected); + expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + + // Unicode and special values + expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); + expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); + expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + + // Fragment positioning (fragments are preserved by default) + expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); + expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + }); + + it("should strip all query parameters from the URL for `stripQueryParamsForDomains` (edge cases)", function () { + const stripAll = (url) => neuwo.cleanUrl(url, { stripQueryParamsForDomains: ["example.com"] }); + const expected = "https://example.com/page"; + const expectedWithFragment = "https://example.com/page#anchor"; + + // Basic formats + expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); + expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); + expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); + expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + + // Multiple parameters + expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); + expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + + // Special characters and encoding + expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); + expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); + expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + + // Delimiters and syntax edge cases + expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); + expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + + // Empty and missing cases + expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); + expect(stripAll("https://example.com/page??"), "should remove double question mark").to.equal(expected); + expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + + // Unicode and special values + expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); + expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); + expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + + // Fragment positioning (fragments are preserved by default) + expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); + expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + }); + + it("should strip all query parameters from the URL for `stripQueryParams` (edge cases)", function () { + const stripAll = (url) => neuwo.cleanUrl(url, { stripQueryParams: ["key", "key1", "key2", "", "?"] }); + const expected = "https://example.com/page"; + const expectedWithFragment = "https://example.com/page#anchor"; + + // Basic formats + expect(stripAll("https://example.com/page?key=value"), "should remove basic key=value params").to.equal(expected); + expect(stripAll("https://example.com/page?key="), "should remove params with empty value").to.equal(expected); + expect(stripAll("https://example.com/page?key"), "should remove params without equals sign").to.equal(expected); + expect(stripAll("https://example.com/page?=value"), "should remove params with empty key").to.equal(expected); + + // Multiple parameters + expect(stripAll("https://example.com/page?key1=value1&key2=value2"), "should remove multiple different params").to.equal(expected); + expect(stripAll("https://example.com/page?key=value1&key=value2"), "should remove multiple params with same key").to.equal(expected); + + // Special characters and encoding + expect(stripAll("https://example.com/page?key=value%20with%20spaces"), "should remove URL encoded spaces").to.equal(expected); + expect(stripAll("https://example.com/page?key=value+with+plus"), "should remove plus as space").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%3D%26%3F"), "should remove encoded special chars").to.equal(expected); + expect(stripAll("https://example.com/page?key=%"), "should remove incomplete encoding").to.equal(expected); + expect(stripAll("https://example.com/page?key=value%2"), "should remove malformed encoding").to.equal(expected); + + // Delimiters and syntax edge cases + expect(stripAll("https://example.com/page?&key=value"), "should remove params with leading ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&"), "should remove params with trailing ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value&&key2=value2"), "should remove params with double ampersand").to.equal(expected); + expect(stripAll("https://example.com/page?key=value?key2=value2"), "should remove params with question mark delimiter").to.equal(expected); + expect(stripAll("https://example.com/page?key=value;key2=value2"), "should remove params with semicolon delimiter").to.equal(expected); + + // Empty and missing cases + expect(stripAll("https://example.com/page?"), "should remove question mark alone").to.equal(expected); + expect(stripAll("https://example.com/page"), "should handle URL without query string").to.equal(expected); + + // Unicode and special values + expect(stripAll("https://example.com/page?key=值"), "should remove unicode characters").to.equal(expected); + expect(stripAll("https://example.com/page?key=null"), "should remove string 'null'").to.equal(expected); + expect(stripAll("https://example.com/page?key=undefined"), "should remove string 'undefined'").to.equal(expected); + + // Fragment positioning (fragments are preserved by default) + expect(stripAll("https://example.com/page?key=value#anchor"), "should remove query params and preserve fragment").to.equal(expectedWithFragment); + expect(stripAll("https://example.com/page#anchor?key=value"), "should preserve fragment before params").to.equal("https://example.com/page#anchor?key=value"); + }); + }); + + describe("when stripAllQueryParams is true", function () { + it("should strip all query parameters from the URL", function () { + const url = "https://example.com/page?foo=bar&baz=qux&test=123"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should remove all query parameters").to.equal(expected); + }); + + it("should return the URL unchanged if there are no query parameters", function () { + const url = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should handle URLs without query params").to.equal(url); + }); + + it("should preserve the hash fragment when stripping query params without stripFragments", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page#section"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should preserve hash fragments by default").to.equal(expected); + }); + + it("should strip hash fragment when stripFragments is enabled", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true, stripFragments: true }); + expect(result, "should strip both query params and fragments").to.equal(expected); + }); + + it("should strip query params but preserve path and protocol", function () { + const url = "https://subdomain.example.com:8080/path/to/page?param=value"; + const expected = "https://subdomain.example.com:8080/path/to/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true }); + expect(result, "should preserve protocol, domain, port, and path").to.equal(expected); + }); + }); + + describe("when stripQueryParamsForDomains is provided", function () { + it("should strip all query params for exact domain match", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should strip params for exact domain match").to.equal(expected); + }); + + it("should strip all query params for subdomain match", function () { + const url = "https://sub.example.com/page?foo=bar"; + const expected = "https://sub.example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should strip params for subdomains").to.equal(expected); + }); + + it("should not strip query params if domain does not match", function () { + const url = "https://other.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should preserve params for non-matching domains").to.equal(url); + }); + + it("should not strip query params if subdomain is provided for domain", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["sub.example.com"] + }); + expect(result, "should preserve params for domain when subdomain is provided").to.equal(url); + }); + + it("should handle multiple domains in the list", function () { + const url1 = "https://example.com/page?foo=bar"; + const url2 = "https://test.com/page?foo=bar"; + const url3 = "https://other.com/page?foo=bar"; + const domains = ["example.com", "test.com"]; + + const result1 = neuwo.cleanUrl(url1, { stripQueryParamsForDomains: domains }); + const result2 = neuwo.cleanUrl(url2, { stripQueryParamsForDomains: domains }); + const result3 = neuwo.cleanUrl(url3, { stripQueryParamsForDomains: domains }); + + expect(result1, "should strip params for first domain").to.equal("https://example.com/page"); + expect(result2, "should strip params for second domain").to.equal("https://test.com/page"); + expect(result3, "should preserve params for non-listed domain").to.equal(url3); + }); + + it("should handle deep subdomains correctly", function () { + const url = "https://deep.sub.example.com/page?foo=bar"; + const expected = "https://deep.sub.example.com/page"; + const result1 = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + const result2 = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["sub.example.com"] + }); + expect(result1, "should strip params for deep subdomains with domain matching").to.equal(expected); + expect(result2, "should strip params for deep subdomains with subdomain matching").to.equal(expected); + }); + + it("should not match partial domain names", function () { + const url = "https://notexample.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"] + }); + expect(result, "should not match partial domain strings").to.equal(url); + }); + + it("should handle empty domain list", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripQueryParamsForDomains: [] }); + expect(result, "should not strip params with empty domain list").to.equal(url); + }); + }); + + describe("when stripQueryParams is provided", function () { + it("should strip only specified query parameters", function () { + const url = "https://example.com/page?foo=bar&baz=qux&keep=this"; + const expected = "https://example.com/page?keep=this"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["foo", "baz"] + }); + expect(result, "should remove only specified params").to.equal(expected); + }); + + it("should handle single parameter stripping", function () { + const url = "https://example.com/page?remove=this&keep=that"; + const expected = "https://example.com/page?keep=that"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["remove"] + }); + expect(result, "should remove single specified param").to.equal(expected); + }); + + it("should return URL without query string if all params are stripped", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["foo", "baz"] + }); + expect(result, "should remove query string when all params stripped").to.equal(expected); + }); + + it("should handle case where specified params do not exist", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["nonexistent", "alsonothere"] + }); + expect(result, "should handle non-existent params gracefully").to.equal(url); + }); + + it("should handle empty param list", function () { + const url = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripQueryParams: [] }); + expect(result, "should not strip params with empty list").to.equal(url); + }); + + it("should preserve param order for remaining params", function () { + const url = "https://example.com/page?a=1&b=2&c=3&d=4"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["b", "d"] + }); + expect(result, "should preserve order of remaining params").to.include("a=1"); + expect(result, "should preserve order of remaining params").to.include("c=3"); + expect(result, "should not include stripped param b").to.not.include("b=2"); + expect(result, "should not include stripped param d").to.not.include("d=4"); + }); + }); + + describe("error handling", function () { + it("should return null or undefined input unchanged", function () { + expect(neuwo.cleanUrl(null, {}), "should handle null input").to.equal(null); + expect(neuwo.cleanUrl(undefined, {}), "should handle undefined input").to.equal(undefined); + expect(neuwo.cleanUrl("", {}), "should handle empty string").to.equal(""); + }); + + it("should return invalid URL unchanged and log error", function () { + const invalidUrl = "not-a-valid-url"; + const result = neuwo.cleanUrl(invalidUrl, { stripAllQueryParams: true }); + expect(result, "should return invalid URL unchanged").to.equal(invalidUrl); + }); + + it("should handle malformed URLs gracefully", function () { + const malformedUrl = "http://"; + const result = neuwo.cleanUrl(malformedUrl, { stripAllQueryParams: true }); + expect(result, "should return malformed URL unchanged").to.equal(malformedUrl); + }); + }); + + describe("when stripFragments is enabled", function () { + it("should strip URL fragments from URLs without query params", function () { + const url = "https://example.com/page#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove hash fragment").to.equal(expected); + }); + + it("should strip URL fragments from URLs with query params", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove hash fragment and preserve query params").to.equal(expected); + }); + + it("should strip fragments when combined with stripAllQueryParams", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripAllQueryParams: true, stripFragments: true }); + expect(result, "should remove both query params and fragment").to.equal(expected); + }); + + it("should strip fragments when combined with stripQueryParamsForDomains", function () { + const url = "https://example.com/page?foo=bar#section"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"], + stripFragments: true + }); + expect(result, "should remove both query params and fragment for matching domain").to.equal(expected); + }); + + it("should strip fragments when combined with stripQueryParams", function () { + const url = "https://example.com/page?foo=bar&keep=this#section"; + const expected = "https://example.com/page?keep=this"; + const result = neuwo.cleanUrl(url, { + stripQueryParams: ["foo"], + stripFragments: true + }); + expect(result, "should remove specified query params and fragment").to.equal(expected); + }); + + it("should handle URLs without fragments gracefully", function () { + const url = "https://example.com/page?foo=bar"; + const expected = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should handle URLs without fragments").to.equal(expected); + }); + + it("should handle empty fragments", function () { + const url = "https://example.com/page#"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove empty fragment").to.equal(expected); + }); + + it("should handle complex fragments with special characters", function () { + const url = "https://example.com/page?foo=bar#section-1/subsection?query"; + const expected = "https://example.com/page?foo=bar"; + const result = neuwo.cleanUrl(url, { stripFragments: true }); + expect(result, "should remove complex fragments").to.equal(expected); + }); + }); + + describe("option priority", function () { + it("should apply stripAllQueryParams first when multiple options are set", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripAllQueryParams: true, + stripQueryParams: ["foo"] + }); + expect(result, "stripAllQueryParams should take precedence").to.equal(expected); + }); + + it("should apply stripQueryParamsForDomains before stripQueryParams", function () { + const url = "https://example.com/page?foo=bar&baz=qux"; + const expected = "https://example.com/page"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"], + stripQueryParams: ["foo"] + }); + expect(result, "domain-specific stripping should take precedence").to.equal(expected); + }); + + it("should not strip for non-matching domain even with stripQueryParams set", function () { + const url = "https://other.com/page?foo=bar&baz=qux"; + const expected = "https://other.com/page?baz=qux"; + const result = neuwo.cleanUrl(url, { + stripQueryParamsForDomains: ["example.com"], + stripQueryParams: ["foo"] + }); + expect(result, "should fall through to stripQueryParams for non-matching domain").to.equal(expected); + }); + }); + }); + + // Integration Tests describe("injectIabCategories edge cases and merging", function () { it("should not inject data if 'marketing_categories' is missing from the successful API response", function () { const apiResponse = { brand_safety: { BS_score: "1.0" } }; // Missing marketing_categories @@ -584,4 +1022,281 @@ describe("neuwoRtdModule", function () { ); }); }); + + describe("getBidRequestData with caching", function () { + describe("when enableCache is true (default)", function () { + it("should cache the API response and reuse it on subsequent calls", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=1"; + + // First call should make an API request + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call should use cached response (no new API request) + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should not make a new API request").to.equal(1); + + // Both configs should have the same data + const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; + expect(contentData1, "First config should have Neuwo data").to.exist; + expect(contentData2, "Second config should have Neuwo data from cache").to.exist; + expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + }); + + it("should cache when enableCache is explicitly set to true", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=2"; + conf.params.enableCache = true; + + // First call + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should use cached response").to.equal(1); + }); + }); + + describe("when enableCache is false", function () { + it("should not cache the API response and make a new request each time", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=3"; + conf.params.enableCache = false; + + // First call should make an API request + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call should make a new API request (not use cache) + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should make a new API request").to.equal(2); + + const request2 = server.requests[1]; + request2.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Both configs should have the same data structure + const contentData1 = bidsConfig1.ortb2Fragments.global.site.content.data[0]; + const contentData2 = bidsConfig2.ortb2Fragments.global.site.content.data[0]; + expect(contentData1, "First config should have Neuwo data").to.exist; + expect(contentData2, "Second config should have Neuwo data from new request").to.exist; + expect(contentData1.name, "First config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + expect(contentData2.name, "Second config should have correct provider").to.equal(neuwo.DATA_PROVIDER); + }); + + it("should bypass existing cache when enableCache is false", function () { + const apiResponse = getNeuwoApiResponse(); + const bidsConfig1 = bidsConfiglike(); + const bidsConfig2 = bidsConfiglike(); + const bidsConfig3 = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?id=4"; + + // First call with caching enabled (default) + neuwo.getBidRequestData(bidsConfig1, () => {}, conf, "consent data"); + expect(server.requests.length, "First call should make an API request").to.equal(1); + + const request1 = server.requests[0]; + request1.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + + // Second call with caching enabled should use cache + neuwo.getBidRequestData(bidsConfig2, () => {}, conf, "consent data"); + expect(server.requests.length, "Second call should use cache").to.equal(1); + + // Third call with caching disabled should bypass cache + conf.params.enableCache = false; + neuwo.getBidRequestData(bidsConfig3, () => {}, conf, "consent data"); + expect(server.requests.length, "Third call should bypass cache and make new request").to.equal(2); + + const request2 = server.requests[1]; + request2.respond( + 200, + { "Content-Type": "application/json; encoding=UTF-8" }, + JSON.stringify(apiResponse) + ); + }); + }); + }); + + describe("getBidRequestData with URL query param stripping", function () { + describe("when stripAllQueryParams is enabled", function () { + it("should strip all query parameters from the analyzed URL", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test&utm_campaign=example&id=5"; + conf.params.stripAllQueryParams = true; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should not contain encoded query params").to.include( + encodeURIComponent("https://publisher.works/article.php") + ); + expect(request.url, "The request URL should not contain utm_source").to.not.include( + encodeURIComponent("utm_source") + ); + }); + }); + + describe("when stripQueryParamsForDomains is enabled", function () { + it("should strip query params only for matching domains", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?foo=bar&id=5"; + conf.params.stripQueryParamsForDomains = ["publisher.works"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the URL without query params").to.include( + encodeURIComponent("https://publisher.works/article.php") + ); + expect(request.url, "The request URL should not contain the id param").to.not.include( + encodeURIComponent("id=5") + ); + }); + + it("should not strip query params for non-matching domains", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://other-domain.com/page?foo=bar&id=5"; + conf.params.stripQueryParamsForDomains = ["publisher.works"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the full URL with query params").to.include( + encodeURIComponent("https://other-domain.com/page?foo=bar&id=5") + ); + }); + + it("should handle subdomain matching correctly", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://sub.publisher.works/page?tracking=123"; + conf.params.stripQueryParamsForDomains = ["publisher.works"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should strip params for subdomain").to.include( + encodeURIComponent("https://sub.publisher.works/page") + ); + expect(request.url, "The request URL should not contain tracking param").to.not.include( + encodeURIComponent("tracking=123") + ); + }); + }); + + describe("when stripQueryParams is enabled", function () { + it("should strip only specified query parameters", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test&utm_campaign=example&id=5"; + conf.params.stripQueryParams = ["utm_source", "utm_campaign"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the id param").to.include( + encodeURIComponent("id=5") + ); + expect(request.url, "The request URL should not contain utm_source").to.not.include( + encodeURIComponent("utm_source") + ); + expect(request.url, "The request URL should not contain utm_campaign").to.not.include( + encodeURIComponent("utm_campaign") + ); + }); + + it("should handle stripping params that result in no query string", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + conf.params.websiteToAnalyseUrl = "https://publisher.works/article.php?utm_source=test"; + conf.params.stripQueryParams = ["utm_source"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should not contain a query string").to.include( + encodeURIComponent("https://publisher.works/article.php") + ); + expect(request.url, "The request URL should not contain utm_source").to.not.include( + encodeURIComponent("utm_source") + ); + }); + + it("should leave URL unchanged if specified params do not exist", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + const originalUrl = "https://publisher.works/article.php?id=5"; + conf.params.websiteToAnalyseUrl = originalUrl; + conf.params.stripQueryParams = ["utm_source", "nonexistent"]; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the original URL").to.include( + encodeURIComponent(originalUrl) + ); + }); + }); + + describe("when no stripping options are provided", function () { + it("should send the URL with all query parameters intact", function () { + const bidsConfig = bidsConfiglike(); + const conf = config(); + const originalUrl = "https://publisher.works/article.php?get=horrible_url_for_testing&id=5"; + conf.params.websiteToAnalyseUrl = originalUrl; + + neuwo.getBidRequestData(bidsConfig, () => {}, conf, "consent data"); + const request = server.requests[0]; + + expect(request.url, "The request URL should contain the full original URL").to.include( + encodeURIComponent(originalUrl) + ); + }); + }); + }); }); From 3f1c0dceb3e0aed142a23453dd7bda969f5b836b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:26:12 -0500 Subject: [PATCH 121/147] Bump min-document from 2.19.0 to 2.19.2 (#14162) Bumps [min-document](https://github.com/Raynos/min-document) from 2.19.0 to 2.19.2. - [Commits](https://github.com/Raynos/min-document/compare/v2.19.0...v2.19.2) --- updated-dependencies: - dependency-name: min-document dependency-version: 2.19.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 853f63ae7ff..26e2fabf732 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15568,8 +15568,11 @@ } }, "node_modules/min-document": { - "version": "2.19.0", + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", "dev": true, + "license": "MIT", "dependencies": { "dom-walk": "^0.1.0" } From 71471301137938b831b0d19c78593a4b49ff42e7 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 19 Nov 2025 10:56:02 -0500 Subject: [PATCH 122/147] CI: reduce dependency on browserstack (#14165) * try safari testing * try macos-latest * adjust save/load * adjust again * why * adjust -C * try safarinative * use SafariNative * separate build from run-tests * browser tests * add safari * refactor * use setup step * add firefoxHeadless * add edge * try force-local * temporarily remove build logic testing * --force-local only on win * add npm install to build * run npm install on windows * use script to generate id * use --force-local on save as well * setup edge * remove run npm install option * enable debug logging * try edge headless * define EdgeHeadless * try chromium edge * remove edge launcher * disable debug logging * add build logic test step * set shell bash * move id generation to actions/save * browser_testing.json * fix coverage * add browerstack * fix bstack secrets * remove unnecessary checkout * Clean up unused input * try clearing localStorage --- .github/actions/load/action.yml | 16 ++- .github/actions/save/action.yml | 28 ++++- .github/workflows/browser-tests.yml | 116 +++++++++++++++++++++ .github/workflows/browser_testing.json | 17 +++ .github/workflows/build.yml | 43 ++++++++ .github/workflows/run-tests.yml | 54 +++++----- .github/workflows/test.yml | 5 +- gulpfile.js | 2 +- karma.conf.maker.js | 5 +- package-lock.json | 139 ++++++++----------------- package.json | 2 + test/test_deps.js | 2 + 12 files changed, 290 insertions(+), 139 deletions(-) create mode 100644 .github/workflows/browser-tests.yml create mode 100644 .github/workflows/browser_testing.json create mode 100644 .github/workflows/build.yml diff --git a/.github/actions/load/action.yml b/.github/actions/load/action.yml index 85ea9009a61..0102608dbd1 100644 --- a/.github/actions/load/action.yml +++ b/.github/actions/load/action.yml @@ -11,11 +11,17 @@ runs: uses: actions/setup-node@v6 with: node-version: '20' - + - uses: actions/github-script@v8 + id: platform + with: + result-encoding: string + script: | + const os = require('os'); + return os.platform(); - name: 'Clear working directory' shell: bash run: | - rm -r ./* + rm -r "$(pwd)"/* - name: Download artifact uses: actions/download-artifact@v5 @@ -26,5 +32,7 @@ runs: - name: 'Untar working directory' shell: bash run: | - tar -xf '${{ runner.temp }}/${{ inputs.name }}.tar' . - + wdir="$(pwd)" + parent="$(dirname "$wdir")" + target="$(basename "$wdir")" + tar ${{ steps.platform.outputs.result == 'win32' && '--force-local' || '' }} -C "$parent" -xf '${{ runner.temp }}/${{ inputs.name }}.tar' "$target" diff --git a/.github/actions/save/action.yml b/.github/actions/save/action.yml index 3dd8c1f6ea0..3efca584c7f 100644 --- a/.github/actions/save/action.yml +++ b/.github/actions/save/action.yml @@ -1,19 +1,41 @@ name: Save working directory description: Save working directory, preserving permissions inputs: + prefix: + description: Prefix to use for autogenerated names + required: false name: description: a name to reference with actions/load + required: false +outputs: + name: + description: a name to reference with actions/load + value: ${{ fromJSON(steps.platform.outputs.result).name }} runs: using: 'composite' steps: + - uses: actions/github-script@v8 + id: platform + with: + script: | + const os = require('os'); + const crypto = require("crypto"); + const id = crypto.randomBytes(16).toString("hex"); + return { + name: ${{ inputs.name && format('"{0}"', inputs.name) || format('"{0}" + id', inputs.prefix || '') }}, + platform: os.platform(), + } - name: Tar working directory shell: bash run: | - tar -cf "${{ runner.temp }}/${{ inputs.name }}.tar" . + wdir="$(pwd)" + parent="$(dirname "$wdir")" + target="$(basename "$wdir")" + tar ${{ fromJSON(steps.platform.outputs.result).platform == 'win32' && '--force-local' || '' }} -C "$parent" -cf "${{ runner.temp }}/${{ fromJSON(steps.platform.outputs.result).name }}.tar" "$target" - name: Upload artifact uses: actions/upload-artifact@v4 with: - path: '${{ runner.temp }}/${{ inputs.name }}.tar' - name: ${{ inputs.name }} + path: '${{ runner.temp }}/${{ fromJSON(steps.platform.outputs.result).name }}.tar' + name: ${{ fromJSON(steps.platform.outputs.result).name }} overwrite: true diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml new file mode 100644 index 00000000000..662bf38d9f1 --- /dev/null +++ b/.github/workflows/browser-tests.yml @@ -0,0 +1,116 @@ +name: Run unit tests on all browsers +on: + workflow_call: + inputs: + chunks: + description: Number of chunks to split tests into + required: false + type: number + default: 1 + build-cmd: + description: Build command, run once + required: false + type: string + test-cmd: + description: Test command, run once per chunk + required: true + type: string + timeout: + description: Timeout on test run + required: false + type: number + default: 10 + outputs: + coverage: + description: Artifact name for coverage results + value: ${{ jobs.browser-tests.outputs.coverage }} + secrets: + BROWSERSTACK_USER_NAME: + description: "Browserstack user name" + BROWSERSTACK_ACCESS_KEY: + description: "Browserstack access key" +jobs: + build: + uses: ./.github/workflows/build.yml + with: + build-cmd: ${{ inputs.build-cmd }} + + setup: + needs: build + name: "Setup environment" + runs-on: ubuntu-latest + outputs: + browsers: ${{ toJSON(fromJSON(steps.define.outputs.result).browsers) }} + bstack-key: ${{ steps.bstack-save.outputs.name }} + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Restore working directory + uses: ./.github/actions/load + with: + name: ${{ needs.build.outputs.built-key }} + - name: "Define testing strategy" + uses: actions/github-script@v8 + id: define + with: + script: | + const fs = require('node:fs/promises'); + const browsers = require('./.github/workflows/browser_testing.json'); + const excludeFromBstack = Object.values(browsers).map(browser => browser.bsName); + const bstackBrowsers = Object.fromEntries( + // exlude "latest" version of browsers that we can test on GH actions + Object.entries(require('./browsers.json')) + .filter(([name, def]) => !excludeFromBstack.includes(def.browser) || def.browser_version !== 'latest') + ) + const updatedBrowsersJson = JSON.stringify(bstackBrowsers, null, 2); + console.log("Using browsers.json:", updatedBrowsersJson); + await fs.writeFile('./browsers.json', updatedBrowsersJson); + return { + hasBSBrowsers: Object.keys(bstackBrowsers).length > 0, + browsers: Object.entries(browsers).map(([name, def]) => Object.assign({name}, def)) + } + - name: "Save working directory" + id: bstack-save + if: ${{ fromJSON(steps.define.outputs.result).hasBSBrowsers }} + uses: ./.github/actions/save + with: + prefix: browserstack- + + test-build-logic: + needs: build + name: "Test build logic" + uses: + ./.github/workflows/run-tests.yml + with: + built-key: ${{ needs.build.outputs.built-key }} + test-cmd: gulp test-build-logic + + browser-tests: + needs: [setup, build] + name: "Browser: ${{ matrix.browser.name }}" + strategy: + fail-fast: false + matrix: + browser: ${{ fromJSON(needs.setup.outputs.browsers) }} + uses: + ./.github/workflows/run-tests.yml + with: + built-key: ${{ needs.build.outputs.built-key }} + test-cmd: ${{ inputs.test-cmd }} --browsers ${{ matrix.browser.name }} ${{ matrix.browser.coverage && '--coverage' || '--no-coverage' }} + chunks: ${{ inputs.chunks }} + runs-on: ${{ matrix.browser.runsOn || 'ubuntu-latest' }} + + browserstack-tests: + needs: setup + if: ${{ needs.setup.outputs.bstack-key }} + name: "Browserstack tests" + uses: + ./.github/workflows/run-tests.yml + with: + built-key: ${{ needs.setup.outputs.bstack-key }} + test-cmd: ${{ inputs.test-cmd }} --browserstack --no-coverage + chunks: ${{ inputs.chunks }} + browserstack: true + secrets: + BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} diff --git a/.github/workflows/browser_testing.json b/.github/workflows/browser_testing.json new file mode 100644 index 00000000000..aee1631ead3 --- /dev/null +++ b/.github/workflows/browser_testing.json @@ -0,0 +1,17 @@ +{ + "ChromeHeadless": { + "bsName": "chrome", + "coverage": true + }, + "EdgeHeadless": { + "bsName": "edge", + "runsOn": "windows-latest" + }, + "SafariNative": { + "bsName": "safari", + "runsOn": "macos-latest" + }, + "FirefoxHeadless": { + "bsName": "firefox" + } +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..6942d25b54c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +name: Run unit tests +on: + workflow_call: + inputs: + source-key: + description: Artifact name for source directory + type: string + required: false + default: source + build-cmd: + description: Build command + required: false + type: string + outputs: + built-key: + description: Artifact name for built directory + value: ${{ jobs.build.outputs.built-key }} + +jobs: + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + built-key: ${{ inputs.build-cmd && steps.save.outputs.name || inputs.source-key }} + steps: + - name: Checkout + if: ${{ inputs.build-cmd }} + uses: actions/checkout@v5 + - name: Restore source + if: ${{ inputs.build-cmd }} + uses: ./.github/actions/load + with: + name: ${{ inputs.source-key }} + - name: Build + if: ${{ inputs.build-cmd }} + run: ${{ inputs.build-cmd }} + - name: 'Save working directory' + id: save + if: ${{ inputs.build-cmd }} + uses: ./.github/actions/save + with: + prefix: 'build-' diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f76ab69a0ec..26e180f133d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,6 +11,10 @@ on: description: Build command, run once required: false type: string + built-key: + description: Artifact name for built source + required: false + type: string test-cmd: description: Test command, run once per chunk required: true @@ -19,11 +23,17 @@ on: description: If true, set up browserstack environment and adjust concurrency required: false type: boolean + default: false timeout: description: Timeout on test run required: false type: number default: 10 + runs-on: + description: Runner image + required: false + default: ubuntu-latest + type: string outputs: coverage: description: Artifact name for coverage results @@ -35,47 +45,30 @@ on: description: "Browserstack access key" jobs: - build: - name: Build + checkout: + name: "Set up environment" runs-on: ubuntu-latest - timeout-minutes: 5 outputs: chunks: ${{ steps.chunks.outputs.chunks }} - wdir: ${{ inputs.build-cmd && format('build-{0}', inputs.build-cmd) || 'source' }} steps: - - name: Checkout - if: ${{ inputs.build-cmd }} - uses: actions/checkout@v5 - - name: Restore source - if: ${{ inputs.build-cmd }} - uses: ./.github/actions/load - with: - name: source - - - name: Build - if: ${{ inputs.build-cmd }} - run: ${{ inputs.build-cmd }} - - - name: 'Save working directory' - if: ${{ inputs.build-cmd }} - uses: ./.github/actions/save - with: - name: build-${{ inputs.build-cmd }} - - name: Define chunks id: chunks run: | echo 'chunks=[ '$(seq --separator=, 1 1 ${{ inputs.chunks }})' ]' >> "$GITHUB_OUTPUT" - - + + build: + uses: ./.github/workflows/build.yml + with: + build-cmd: ${{ !inputs.built-key && inputs.build-cmd || '' }} + source-key: ${{ inputs.built-key || 'source' }} run-tests: - needs: build + needs: [checkout, build] strategy: fail-fast: false max-parallel: ${{ inputs.browserstack && 1 || inputs.chunks }} matrix: - chunk-no: ${{ fromJSON(needs.build.outputs.chunks) }} + chunk-no: ${{ fromJSON(needs.checkout.outputs.chunks) }} name: "Test chunk ${{ matrix.chunk-no }}" env: @@ -93,7 +86,7 @@ jobs: group: ${{ inputs.browserstack && 'browser' || github.run_id }}${{ inputs.browserstack && 'stac' || inputs.test-cmd }}${{ inputs.browserstack && 'k' || matrix.chunk-no }}-${{ github.run_id }} cancel-in-progress: false - runs-on: ubuntu-latest + runs-on: ${{ inputs.runs-on }} steps: - name: Checkout uses: actions/checkout@v5 @@ -101,7 +94,7 @@ jobs: - name: Restore source uses: ./.github/actions/load with: - name: ${{ needs.build.outputs.wdir }} + name: ${{ needs.build.outputs.built-key }} - name: 'BrowserStack Env Setup' if: ${{ inputs.browserstack }} @@ -137,6 +130,7 @@ jobs: - name: 'Check for coverage' id: 'coverage' + shell: bash run: | if [ -d "./build/coverage" ]; then echo 'coverage=true' >> "$GITHUB_OUTPUT"; @@ -164,7 +158,7 @@ jobs: - name: Restore source uses: ./.github/actions/load with: - name: ${{ needs.build.outputs.wdir }} + name: ${{ needs.build.outputs.built-key }} - name: Download coverage results uses: actions/download-artifact@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ab3aa8249e..658af3a5731 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,12 +90,11 @@ jobs: test: name: "Unit tests (all features enabled + coverage)" needs: checkout - uses: ./.github/workflows/run-tests.yml + uses: ./.github/workflows/browser-tests.yml with: chunks: 8 build-cmd: npx gulp precompile - test-cmd: npx gulp test-only-nobuild --browserstack - browserstack: true + test-cmd: npx gulp test-only-nobuild secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} diff --git a/gulpfile.js b/gulpfile.js index 845b1836733..cd51c89f5e1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -521,7 +521,7 @@ gulp.task('build-bundle-verbose', gulp.series(precompile(), makeWebpackPkg(makeV // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('update-browserslist', execaTask('npx update-browserslist-db@latest')); gulp.task('test-build-logic', execaTask('npx mocha ./test/build-logic')) -gulp.task('test-only-nobuild', gulp.series('test-build-logic', testTaskMaker({coverage: true}))) +gulp.task('test-only-nobuild', gulp.series(testTaskMaker({coverage: argv.coverage ?? true}))) gulp.task('test-only', gulp.series('test-build-logic', 'precompile', test)); gulp.task('test-all-features-disabled-nobuild', testTaskMaker({disableFeatures: helpers.getTestDisableFeatures(), oneBrowser: 'chrome', watch: false})); diff --git a/karma.conf.maker.js b/karma.conf.maker.js index f825b8eac4c..6866b296c3e 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -40,6 +40,7 @@ function newWebpackConfig(codeCoverage, disableFeatures) { function newPluginsArray(browserstack) { var plugins = [ 'karma-chrome-launcher', + 'karma-safarinative-launcher', 'karma-coverage', 'karma-mocha', 'karma-chai', @@ -47,14 +48,14 @@ function newPluginsArray(browserstack) { 'karma-sourcemap-loader', 'karma-spec-reporter', 'karma-webpack', - 'karma-mocha-reporter' + 'karma-mocha-reporter', + '@chiragrupani/karma-chromium-edge-launcher', ]; if (browserstack) { plugins.push('karma-browserstack-launcher'); } plugins.push('karma-firefox-launcher'); plugins.push('karma-opera-launcher'); - plugins.push('karma-safari-launcher'); plugins.push('karma-script-launcher'); return plugins; } diff --git a/package-lock.json b/package-lock.json index 26e2fabf732..aa3b76ff33e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "iab-adcom": "^1.0.6", "iab-native": "^1.0.0", "iab-openrtb": "^1.0.1", + "karma-safarinative-launcher": "^1.1.0", "klona": "^2.0.6", "live-connect-js": "^7.2.0" }, @@ -32,6 +33,7 @@ "@babel/eslint-parser": "^7.16.5", "@babel/plugin-transform-runtime": "^7.27.4", "@babel/register": "^7.28.3", + "@chiragrupani/karma-chromium-edge-launcher": "^2.4.1", "@eslint/compat": "^1.3.1", "@types/google-publisher-tag": "^1.20250210.0", "@wdio/browserstack-service": "^9.19.1", @@ -75,6 +77,7 @@ "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.0.1", "karma-coverage-istanbul-reporter": "^3.0.3", + "karma-edge-launcher": "^0.4.2", "karma-firefox-launcher": "^2.1.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", @@ -1610,9 +1613,14 @@ "uuid": "9.0.1" } }, + "node_modules/@chiragrupani/karma-chromium-edge-launcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@chiragrupani/karma-chromium-edge-launcher/-/karma-chromium-edge-launcher-2.4.1.tgz", + "integrity": "sha512-HwTlN4dk7dnL9m5nEonq7cI3Wa787wYfGVWeb4oWPMySIEhFpA7/BYQ8zMbpQ4YkSQxVnvY1502aWdbI3w7DeA==", + "dev": true + }, "node_modules/@colors/colors": { "version": "1.5.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.1.90" @@ -2987,7 +2995,6 @@ }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", - "dev": true, "license": "MIT" }, "node_modules/@stylistic/eslint-plugin": { @@ -3056,12 +3063,10 @@ }, "node_modules/@types/cookie": { "version": "0.4.1", - "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.17", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -3149,7 +3154,6 @@ }, "node_modules/@types/node": { "version": "20.14.2", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -6105,7 +6109,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6131,7 +6134,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -6768,7 +6770,6 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "dev": true, "license": "MIT" }, "node_modules/bare-events": { @@ -6859,7 +6860,6 @@ }, "node_modules/base64id": { "version": "2.0.0", - "dev": true, "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" @@ -6913,7 +6913,6 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7056,7 +7055,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7065,7 +7063,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -7274,9 +7271,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "version": "1.0.30001755", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz", + "integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==", "funding": [ { "type": "opencollective", @@ -7393,7 +7390,6 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -7718,7 +7714,6 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, "license": "MIT" }, "node_modules/concat-with-sourcemaps": { @@ -7731,7 +7726,6 @@ }, "node_modules/connect": { "version": "3.7.0", - "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -7753,7 +7747,6 @@ }, "node_modules/connect/node_modules/debug": { "version": "2.6.9", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -7761,7 +7754,6 @@ }, "node_modules/connect/node_modules/finalhandler": { "version": "1.1.2", - "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -7778,12 +7770,10 @@ }, "node_modules/connect/node_modules/ms": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/connect/node_modules/on-finished": { "version": "2.3.0", - "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -7794,7 +7784,6 @@ }, "node_modules/connect/node_modules/statuses": { "version": "1.5.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7886,7 +7875,6 @@ }, "node_modules/cors": { "version": "2.8.5", - "dev": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -8373,7 +8361,6 @@ }, "node_modules/custom-event": { "version": "1.0.1", - "dev": true, "license": "MIT" }, "node_modules/d": { @@ -8446,7 +8433,6 @@ }, "node_modules/date-format": { "version": "4.0.14", - "dev": true, "license": "MIT", "engines": { "node": ">=4.0" @@ -8725,7 +8711,6 @@ }, "node_modules/di": { "version": "0.0.1", - "dev": true, "license": "MIT" }, "node_modules/diff": { @@ -8763,7 +8748,6 @@ }, "node_modules/dom-serialize": { "version": "2.2.1", - "dev": true, "license": "MIT", "dependencies": { "custom-event": "~1.0.0", @@ -8916,6 +8900,12 @@ "wcwidth": "^1.0.1" } }, + "node_modules/edge-launcher": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/edge-launcher/-/edge-launcher-1.2.2.tgz", + "integrity": "sha512-JcD5WBi3BHZXXVSSeEhl6sYO8g5cuynk/hifBzds2Bp4JdzCGLNMHgMCKu5DvrO1yatMgF0goFsxXRGus0yh1g==", + "dev": true + }, "node_modules/edge-paths": { "version": "3.0.5", "dev": true, @@ -9048,7 +9038,6 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -9061,7 +9050,6 @@ }, "node_modules/encodeurl": { "version": "1.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -9100,7 +9088,6 @@ }, "node_modules/engine.io": { "version": "6.6.2", - "dev": true, "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -9120,7 +9107,6 @@ }, "node_modules/engine.io-parser": { "version": "5.2.3", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -9128,7 +9114,6 @@ }, "node_modules/engine.io/node_modules/cookie": { "version": "0.7.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9150,7 +9135,6 @@ }, "node_modules/ent": { "version": "2.2.0", - "dev": true, "license": "MIT" }, "node_modules/entities": { @@ -10517,7 +10501,6 @@ }, "node_modules/eventemitter3": { "version": "4.0.7", - "dev": true, "license": "MIT" }, "node_modules/events": { @@ -10752,7 +10735,6 @@ }, "node_modules/extend": { "version": "3.0.2", - "dev": true, "license": "MIT" }, "node_modules/extend-shallow": { @@ -11032,7 +11014,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -11162,12 +11143,10 @@ }, "node_modules/flatted": { "version": "3.3.1", - "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.6", - "dev": true, "funding": [ { "type": "individual", @@ -11343,7 +11322,6 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -11476,7 +11454,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -11645,7 +11622,6 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -12735,7 +12711,6 @@ }, "node_modules/http-proxy": { "version": "1.18.1", - "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", @@ -12908,7 +12883,6 @@ }, "node_modules/inflight": { "version": "1.0.6", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -13080,7 +13054,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -13218,7 +13191,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13240,7 +13212,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13267,7 +13238,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -13312,7 +13282,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -13566,7 +13535,6 @@ }, "node_modules/isbinaryfile": { "version": "4.0.10", - "dev": true, "license": "MIT", "engines": { "node": ">= 8.0.0" @@ -14552,7 +14520,6 @@ "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", - "dev": true, "license": "MIT", "dependencies": { "@colors/colors": "1.5.0", @@ -14758,6 +14725,21 @@ "semver": "bin/semver" } }, + "node_modules/karma-edge-launcher": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz", + "integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==", + "dev": true, + "dependencies": { + "edge-launcher": "1.2.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, "node_modules/karma-firefox-launcher": { "version": "2.1.3", "dev": true, @@ -14836,8 +14818,17 @@ }, "node_modules/karma-safari-launcher": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", + "integrity": "sha512-qmypLWd6F2qrDJfAETvXDfxHvKDk+nyIjpH9xIeI3/hENr0U3nuqkxaftq73PfXZ4aOuOChA6SnLW4m4AxfRjQ==", "dev": true, - "license": "MIT", + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-safarinative-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-safarinative-launcher/-/karma-safarinative-launcher-1.1.0.tgz", + "integrity": "sha512-vdMjdQDHkSUbOZc8Zq2K5bBC0yJGFEgfrKRJTqt0Um0SC1Rt8drS2wcN6UA3h4LgsL3f1pMcmRSvKucbJE8Qdg==", "peerDependencies": { "karma": ">=0.9" } @@ -14954,7 +14945,6 @@ }, "node_modules/karma/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -14968,7 +14958,6 @@ }, "node_modules/karma/node_modules/cliui": { "version": "7.0.4", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -14978,7 +14967,6 @@ }, "node_modules/karma/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -14989,12 +14977,10 @@ }, "node_modules/karma/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/karma/node_modules/glob": { "version": "7.2.3", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -15013,7 +14999,6 @@ }, "node_modules/karma/node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15024,7 +15009,6 @@ }, "node_modules/karma/node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -15040,7 +15024,6 @@ }, "node_modules/karma/node_modules/yargs": { "version": "16.2.0", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^7.0.2", @@ -15057,7 +15040,6 @@ }, "node_modules/karma/node_modules/yargs-parser": { "version": "20.2.9", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -15320,7 +15302,6 @@ }, "node_modules/log4js": { "version": "6.9.1", - "dev": true, "license": "Apache-2.0", "dependencies": { "date-format": "^4.0.14", @@ -15541,7 +15522,6 @@ }, "node_modules/mime": { "version": "2.6.0", - "dev": true, "license": "MIT", "bin": { "mime": "cli.js" @@ -15579,7 +15559,6 @@ }, "node_modules/minimatch": { "version": "3.1.2", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -15590,7 +15569,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15612,7 +15590,6 @@ }, "node_modules/mkdirp": { "version": "0.5.6", - "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -16401,7 +16378,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16450,7 +16426,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16618,7 +16593,6 @@ }, "node_modules/once": { "version": "1.4.0", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -16935,7 +16909,6 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17039,7 +17012,6 @@ }, "node_modules/picomatch": { "version": "2.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -17419,7 +17391,6 @@ }, "node_modules/qjobs": { "version": "1.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.9" @@ -17652,7 +17623,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -17811,7 +17781,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17827,7 +17796,6 @@ }, "node_modules/requires-port": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/resolve": { @@ -17918,7 +17886,6 @@ }, "node_modules/rfdc": { "version": "1.4.1", - "dev": true, "license": "MIT" }, "node_modules/rgb2hex": { @@ -17928,7 +17895,6 @@ }, "node_modules/rimraf": { "version": "3.0.2", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -17942,7 +17908,6 @@ }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -18690,7 +18655,6 @@ }, "node_modules/socket.io": { "version": "4.8.0", - "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.4", @@ -18707,7 +18671,6 @@ }, "node_modules/socket.io-adapter": { "version": "2.5.5", - "dev": true, "license": "MIT", "dependencies": { "debug": "~4.3.4", @@ -18716,7 +18679,6 @@ }, "node_modules/socket.io-parser": { "version": "4.2.4", - "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -18767,7 +18729,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -19024,7 +18985,6 @@ }, "node_modules/streamroller": { "version": "3.1.5", - "dev": true, "license": "MIT", "dependencies": { "date-format": "^4.0.14", @@ -19037,7 +18997,6 @@ }, "node_modules/streamroller/node_modules/fs-extra": { "version": "8.1.0", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -19050,7 +19009,6 @@ }, "node_modules/streamroller/node_modules/jsonfile": { "version": "4.0.0", - "dev": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" @@ -19058,7 +19016,6 @@ }, "node_modules/streamroller/node_modules/universalify": { "version": "0.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -19098,7 +19055,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -19136,7 +19092,6 @@ }, "node_modules/string-width/node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -19720,7 +19675,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.14" @@ -19741,7 +19695,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -20062,7 +20015,6 @@ }, "node_modules/ua-parser-js": { "version": "0.7.38", - "dev": true, "funding": [ { "type": "opencollective", @@ -20159,7 +20111,6 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -20639,7 +20590,6 @@ }, "node_modules/void-elements": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21693,12 +21643,10 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, "license": "ISC" }, "node_modules/ws": { "version": "8.17.1", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -21725,7 +21673,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, "license": "ISC", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 7a467835e88..e90363ae71b 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@babel/eslint-parser": "^7.16.5", "@babel/plugin-transform-runtime": "^7.27.4", "@babel/register": "^7.28.3", + "@chiragrupani/karma-chromium-edge-launcher": "^2.4.1", "@eslint/compat": "^1.3.1", "@types/google-publisher-tag": "^1.20250210.0", "@wdio/browserstack-service": "^9.19.1", @@ -156,6 +157,7 @@ "iab-adcom": "^1.0.6", "iab-native": "^1.0.0", "iab-openrtb": "^1.0.1", + "karma-safarinative-launcher": "^1.1.0", "klona": "^2.0.6", "live-connect-js": "^7.2.0" }, diff --git a/test/test_deps.js b/test/test_deps.js index e35e813a574..7047e775d9c 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -39,6 +39,8 @@ sinon.useFakeXMLHttpRequest = fakeXhr.useFakeXMLHttpRequest.bind(fakeXhr); sinon.createFakeServer = fakeServer.create.bind(fakeServer); sinon.createFakeServerWithClock = fakeServerWithClock.create.bind(fakeServerWithClock); +localStorage.clear(); + require('test/helpers/global_hooks.js'); require('test/helpers/consentData.js'); require('test/helpers/prebidGlobal.js'); From 5e0f5e90c297838fa77a1e6589043b33a69e0cc5 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 19 Nov 2025 13:20:15 -0500 Subject: [PATCH 123/147] Core: fix schema-utils import (#14168) Co-authored-by: Demetrio Girardi --- customize/buildOptions.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/customize/buildOptions.mjs b/customize/buildOptions.mjs index 7b8013ab269..3341f35b0e0 100644 --- a/customize/buildOptions.mjs +++ b/customize/buildOptions.mjs @@ -1,8 +1,12 @@ import path from 'path' -import validate from 'schema-utils' +import { validate } from 'schema-utils' const boModule = path.resolve(import.meta.dirname, '../dist/src/buildOptions.mjs') +/** + * Resolve the absolute path of the default build options module. + * @returns {string} Absolute path to the generated build options module. + */ export function getBuildOptionsModule () { return boModule } @@ -25,6 +29,11 @@ const schema = { } } +/** + * Validate and load build options overrides. + * @param {object} [options] user supplied overrides + * @returns {Promise} Promise resolving to merged build options. + */ export function getBuildOptions (options = {}) { validate(schema, options, { name: 'Prebid build options', From 66eee7d323534ebd2beaadcadf921cea34778b9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:26:36 -0500 Subject: [PATCH 124/147] Bump actions/download-artifact from 5 to 6 (#14146) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-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> Co-authored-by: Patrick McCann --- .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 26e180f133d..c1bb56f931c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -161,7 +161,7 @@ jobs: name: ${{ needs.build.outputs.built-key }} - name: Download coverage results - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: ./build/coverage pattern: coverage-partial-${{ inputs.test-cmd }}-* From c644617045b3b9e21827bd7602e01fec25dfb299 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:24:41 -0500 Subject: [PATCH 125/147] Bump tar-fs from 3.0.9 to 3.1.1 (#14163) Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 3.0.9 to 3.1.1. - [Commits](https://github.com/mafintosh/tar-fs/compare/v3.0.9...v3.1.1) --- updated-dependencies: - dependency-name: tar-fs dependency-version: 3.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Patrick McCann --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index aa3b76ff33e..26e683e4776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19351,7 +19351,9 @@ } }, "node_modules/tar-fs": { - "version": "3.0.9", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "dev": true, "license": "MIT", "dependencies": { From 383af81b38a5cb5c4eb2defedeee0ac3de8305d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:26:01 -0500 Subject: [PATCH 126/147] Bump js-yaml (#14164) Bumps and [js-yaml](https://github.com/nodeca/js-yaml). These dependencies needed to be updated together. Updates `js-yaml` from 3.14.1 to 3.14.2 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2) Updates `js-yaml` from 4.1.0 to 4.1.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect - dependency-name: js-yaml dependency-version: 4.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26e683e4776..a32ff166351 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1820,9 +1820,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -7917,10 +7917,11 @@ "dev": true }, "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -14396,7 +14397,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -15779,7 +15782,9 @@ } }, "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { From d2e377973c819cd211a1a23bc22ed3fd2525d826 Mon Sep 17 00:00:00 2001 From: aaronkoss Date: Wed, 19 Nov 2025 14:27:44 -0600 Subject: [PATCH 127/147] Public Good Bid Adapter : initial release (#13896) * Public Good new adapter * fix typo --------- Co-authored-by: Patrick McCann --- modules/publicGoodBidAdapter.js | 83 ++++++++ modules/publicGoodBigAdapter.md | 33 +++ .../spec/modules/publicGoodBidAdapter_spec.js | 188 ++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 modules/publicGoodBidAdapter.js create mode 100644 modules/publicGoodBigAdapter.md create mode 100644 test/spec/modules/publicGoodBidAdapter_spec.js diff --git a/modules/publicGoodBidAdapter.js b/modules/publicGoodBidAdapter.js new file mode 100644 index 00000000000..b5fa56d7a53 --- /dev/null +++ b/modules/publicGoodBidAdapter.js @@ -0,0 +1,83 @@ +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'publicgood'; +const PUBLIC_GOOD_ENDPOINT = 'https://advice.pgs.io'; +var PGSAdServed = false; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + + isBidRequestValid: function (bid) { + if (PGSAdServed || !bid.params.partnerId || !bid.params.slotId) { + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let partnerId = ""; + let slotId = ""; + + if (validBidRequests[0] && validBidRequests[0].params) { + partnerId = validBidRequests[0].params.partnerId; + slotId = validBidRequests[0].params.slotId; + } + + let payload = { + url: bidderRequest.refererInfo.page || bidderRequest.refererInfo.referer, + partner_id: partnerId, + isprebid: true, + slotid: slotId, + bidRequest: validBidRequests[0] + } + + return { + method: 'POST', + url: PUBLIC_GOOD_ENDPOINT, + data: payload, + options: { + withCredentials: false, + }, + bidId: validBidRequests[0].bidId + } + }, + + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + let bidResponses = []; + let bidResponse = {}; + let partnerId = serverBody && serverBody.targetData ? serverBody.targetData.partner_id : "error"; + + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + if (serverBody.action !== 'Hide' && !PGSAdServed) { + bidResponse.requestId = bidRequest.bidId; + bidResponse.creativeId = serverBody.targetData.target_id; + bidResponse.cpm = serverBody.targetData.cpm; + bidResponse.width = 320; + bidResponse.height = 470; + bidResponse.ad = `
`; + bidResponse.currency = 'USD'; + bidResponse.netRevenue = true; + bidResponse.ttl = 360; + bidResponse.meta = {advertiserDomains: []}; + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + + onBidWon: function(bid) { + // Win once per page load + PGSAdServed = true; + } + +}; +registerBidder(spec); diff --git a/modules/publicGoodBigAdapter.md b/modules/publicGoodBigAdapter.md new file mode 100644 index 00000000000..09fb0879edf --- /dev/null +++ b/modules/publicGoodBigAdapter.md @@ -0,0 +1,33 @@ +# Overview + +**Module Name**: Public Good Bidder Adapter\ +**Module Type**: Bidder Adapter\ +**Maintainer**: publicgood@publicgood.com + +# Description + +Public Good's bid adapter is for use with approved publishers only. Any publisher who wishes to integrate with Pubic Good using the this adapter will need a partner ID. + +Please contact Public Good for additional information and a negotiated set of slots. + +# Test Parameters +``` +{ + bidder: 'publicgood', + params: { + partnerId: 'prebid-test', + slotId: 'test' + } +} +``` + +# Publisher Parameters +``` +{ + bidder: 'publicgood', + params: { + partnerId: '-- partner ID provided by public good --', + slotId: 'all | -- optional slot identifier --' + } +} +``` \ No newline at end of file diff --git a/test/spec/modules/publicGoodBidAdapter_spec.js b/test/spec/modules/publicGoodBidAdapter_spec.js new file mode 100644 index 00000000000..87490ff2086 --- /dev/null +++ b/test/spec/modules/publicGoodBidAdapter_spec.js @@ -0,0 +1,188 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec, storage } from 'modules/publicGoodBidAdapter.js'; +import { hook } from 'src/hook.js'; + +describe('Public Good Adapter', function () { + let validBidRequests; + + beforeEach(function () { + validBidRequests = [ + { + bidder: 'publicgood', + params: { + partnerId: 'prebid-test', + slotId: 'test' + }, + placementCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + }, + ]; + }); + + describe('for requests', function () { + describe('without partner ID', function () { + it('rejects the bid', function () { + const invalidBid = { + bidder: 'publicgood', + params: { + slotId: 'all', + }, + }; + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + + describe('without slot ID', function () { + it('rejects the bid', function () { + const invalidBid = { + bidder: 'publicgood', + params: { + partnerId: 'prebid-test', + }, + }; + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + + describe('with a valid bid', function () { + it('accepts the bid', function () { + const validBid = { + bidder: 'publicgood', + params: { + partnerId: 'prebid-test', + slotId: 'test' + }, + }; + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + }); + }); + + describe('for server responses', function () { + let serverResponse; + + describe('with no body', function () { + beforeEach(function() { + serverResponse = { + body: null, + }; + }); + + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with action=hide', function () { + beforeEach(function() { + serverResponse = { + body: { + action: 'Hide', + }, + }; + }); + + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with a valid campaign', function () { + beforeEach(function() { + serverResponse = { + body: { + "targetData": { + "deviceType": "desktop", + "parent_org": "prebid-test", + "cpm": 3, + "target_id": "a9b430ab-1f62-46f3-9d3a-1ece821dca61", + "deviceInfo": { + "os": { + "name": "Mac OS", + "version": "10.15.7" + }, + "engine": { + "name": "Blink", + "version": "130.0.0.0" + }, + "browser": { + "major": "130", + "name": "Chrome", + "version": "130.0.0.0" + }, + "cpu": {}, + "ua": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/130.0.0.0 Safari\/537.36", + "device": { + "vendor": "Apple", + "model": "Macintosh" + } + }, + "widget_type": "card", + "isInApp": false, + "partner_id": "prebid-test", + "countryCode": "US", + "metroCode": "602", + "hasReadMore": false, + "region": "IL", + "campaign_id": "a9b430ab-1f62-46f3-9d3a-1ece821dca61" + }, + "action": "Default", + "url": "https%3A%2F%2Fpublicgood.com%2F", + "content": { + "parent_org": "prebid-test", + "rules_match_info": null, + "content_id": 20446189, + "all_matches": [ + { + "analysis_tag": "a9b430ab-1f62-46f3-9d3a-1ece821dca61", + "guid": "a9b430ab-1f62-46f3-9d3a-1ece821dca61" + } + ], + "is_override": true, + "cid_match_type": "", + "target_id": "a9b430ab-1f62-46f3-9d3a-1ece821dca61", + "url_id": 128113623, + "title": "Public Good", + "hide": false, + "partner_id": "prebid-test", + "qa_verified": true, + "tag": "a9b430ab-1f62-46f3-9d3a-1ece821dca61", + "is_filter": false + } + } + }; + }); + + it('returns a complete bid', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(1); + expect(bids[0].cpm).to.equal(3); + expect(bids[0].width).to.equal(320); + expect(bids[0].height).to.equal(470); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ad).to.have.string('data-pgs-partner-id="prebid-test"'); + }); + }); + }); +}); From d4fe8de89ee9c55c7aa7c350bd30acb919642372 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 19 Nov 2025 20:53:01 +0000 Subject: [PATCH 128/147] Prebid 10.17.0 release --- .../codeql/queries/autogen_fpDOMMethod.qll | 4 +- .../queries/autogen_fpEventProperty.qll | 16 +- .../queries/autogen_fpGlobalConstructor.qll | 10 +- .../autogen_fpGlobalObjectProperty0.qll | 52 ++-- .../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 | 48 ++- 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 | 8 +- 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/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/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 | 10 +- metadata/modules/appushBidAdapter.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/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 | 13 + metadata/modules/compassBidAdapter.json | 2 +- metadata/modules/conceptxBidAdapter.json | 2 +- metadata/modules/connatixBidAdapter.json | 2 +- metadata/modules/connectIdSystem.json | 2 +- metadata/modules/connectadBidAdapter.json | 2 +- metadata/modules/conversantBidAdapter.json | 25 +- 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/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.json | 6 +- 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 | 23 +- 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/performaxBidAdapter.json | 2 +- .../permutiveIdentityManagerIdSystem.json | 281 +++++++++++++++++- metadata/modules/permutiveRtdProvider.json | 281 +++++++++++++++++- 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/publicGoodBidAdapter.json | 13 + metadata/modules/publinkIdSystem.json | 25 +- 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 | 22 +- 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/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/smarthubBidAdapter.json | 14 + 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 | 19 +- metadata/modules/taboolaIdSystem.json | 19 +- 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 | 18 ++ 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 +- ...er.json => uniquest_widgetBidAdapter.json} | 0 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 | 32 +- package.json | 2 +- 282 files changed, 1117 insertions(+), 404 deletions(-) create mode 100644 metadata/modules/clickioBidAdapter.json create mode 100644 metadata/modules/publicGoodBidAdapter.json create mode 100644 metadata/modules/toponBidAdapter.json rename metadata/modules/{uniquestWidgetBidAdapter.json => uniquest_widgetBidAdapter.json} (100%) diff --git a/.github/codeql/queries/autogen_fpDOMMethod.qll b/.github/codeql/queries/autogen_fpDOMMethod.qll index 388d7fa4fe8..7e21d791a69 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 = "getChannelData" and weight = 827.19 and type = "AudioBuffer" ) + ( this = "toDataURL" and weight = 25.89 and type = "HTMLCanvasElement" ) or - ( this = "toDataURL" and weight = 27.15 and type = "HTMLCanvasElement" ) + ( this = "getChannelData" and weight = 806.52 and type = "AudioBuffer" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpEventProperty.qll b/.github/codeql/queries/autogen_fpEventProperty.qll index db529c7eae5..d136c7a6ab6 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 = 149.23 and event = "devicemotion" ) + ( this = "accelerationIncludingGravity" and weight = 158.1 and event = "devicemotion" ) or - ( this = "beta" and weight = 1075.3 and event = "deviceorientation" ) + ( this = "beta" and weight = 887.22 and event = "deviceorientation" ) or - ( this = "gamma" and weight = 395.62 and event = "deviceorientation" ) + ( this = "gamma" and weight = 361.7 and event = "deviceorientation" ) or - ( this = "alpha" and weight = 366.53 and event = "deviceorientation" ) + ( this = "alpha" and weight = 354.09 and event = "deviceorientation" ) or - ( this = "candidate" and weight = 69.63 and event = "icecandidate" ) + ( this = "candidate" and weight = 69.81 and event = "icecandidate" ) or - ( this = "acceleration" and weight = 58.05 and event = "devicemotion" ) + ( this = "acceleration" and weight = 64.92 and event = "devicemotion" ) or - ( this = "rotationRate" and weight = 57.59 and event = "devicemotion" ) + ( this = "rotationRate" and weight = 64.37 and event = "devicemotion" ) or - ( this = "absolute" and weight = 387.12 and event = "deviceorientation" ) + ( this = "absolute" and weight = 709.73 and event = "deviceorientation" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalConstructor.qll b/.github/codeql/queries/autogen_fpGlobalConstructor.qll index 1c50c5822f0..43213748fa3 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 = "SharedWorker" and weight = 78.14 ) + ( this = "OfflineAudioContext" and weight = 1111.66 ) or - ( this = "OfflineAudioContext" and weight = 1135.77 ) + ( this = "SharedWorker" and weight = 93.35 ) or - ( this = "RTCPeerConnection" and weight = 49.44 ) + ( this = "RTCPeerConnection" and weight = 49.52 ) or - ( this = "Gyroscope" and weight = 142.79 ) + ( this = "Gyroscope" and weight = 98.72 ) or - ( this = "AudioWorkletNode" and weight = 17.63 ) + ( this = "AudioWorkletNode" and weight = 72.93 ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll b/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll index de883c58c8f..bd815ca8cce 100644 --- a/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll +++ b/.github/codeql/queries/autogen_fpGlobalObjectProperty0.qll @@ -7,57 +7,57 @@ class GlobalObjectProperty0 extends string { GlobalObjectProperty0() { - ( this = "availHeight" and weight = 70.68 and global0 = "screen" ) + ( this = "cookieEnabled" and weight = 15.36 and global0 = "navigator" ) or - ( this = "availWidth" and weight = 65.56 and global0 = "screen" ) + ( this = "availHeight" and weight = 69.48 and global0 = "screen" ) or - ( this = "colorDepth" and weight = 34.27 and global0 = "screen" ) + ( this = "availWidth" and weight = 65.15 and global0 = "screen" ) or - ( this = "deviceMemory" and weight = 75.06 and global0 = "navigator" ) + ( this = "colorDepth" and weight = 34.39 and global0 = "screen" ) or - ( this = "availTop" and weight = 1240.09 and global0 = "screen" ) + ( this = "deviceMemory" and weight = 75.15 and global0 = "navigator" ) or - ( this = "cookieEnabled" and weight = 15.3 and global0 = "navigator" ) + ( this = "availTop" and weight = 1256.76 and global0 = "screen" ) or - ( this = "pixelDepth" and weight = 37.72 and global0 = "screen" ) + ( this = "getBattery" and weight = 124.12 and global0 = "navigator" ) or - ( this = "availLeft" and weight = 547.54 and global0 = "screen" ) + ( this = "webdriver" and weight = 30.18 and global0 = "navigator" ) or - ( this = "orientation" and weight = 35.82 and global0 = "screen" ) + ( this = "permission" and weight = 22.23 and global0 = "Notification" ) or - ( this = "vendorSub" and weight = 1791.96 and global0 = "navigator" ) + ( this = "storage" and weight = 170.65 and global0 = "navigator" ) or - ( this = "productSub" and weight = 482.29 and global0 = "navigator" ) + ( this = "orientation" and weight = 38.3 and global0 = "screen" ) or - ( this = "webkitTemporaryStorage" and weight = 40.79 and global0 = "navigator" ) + ( this = "onLine" and weight = 20.05 and global0 = "navigator" ) or - ( this = "hardwareConcurrency" and weight = 67.85 and global0 = "navigator" ) + ( this = "pixelDepth" and weight = 38.22 and global0 = "screen" ) or - ( this = "appCodeName" and weight = 143.58 and global0 = "navigator" ) + ( this = "availLeft" and weight = 539.55 and global0 = "screen" ) or - ( this = "onLine" and weight = 19.76 and global0 = "navigator" ) + ( this = "vendorSub" and weight = 1462.45 and global0 = "navigator" ) or - ( this = "webdriver" and weight = 31.25 and global0 = "navigator" ) + ( this = "productSub" and weight = 525.88 and global0 = "navigator" ) or - ( this = "keyboard" and weight = 957.44 and global0 = "navigator" ) + ( this = "webkitTemporaryStorage" and weight = 40.85 and global0 = "navigator" ) or - ( this = "mediaDevices" and weight = 121.74 and global0 = "navigator" ) + ( this = "hardwareConcurrency" and weight = 70.43 and global0 = "navigator" ) or - ( this = "storage" and weight = 151.33 and global0 = "navigator" ) + ( this = "appCodeName" and weight = 152.93 and global0 = "navigator" ) or - ( this = "mediaCapabilities" and weight = 126.07 and global0 = "navigator" ) + ( this = "keyboard" and weight = 2426.5 and global0 = "navigator" ) or - ( this = "permissions" and weight = 66.75 and global0 = "navigator" ) + ( this = "mediaDevices" and weight = 123.07 and global0 = "navigator" ) or - ( this = "permission" and weight = 22.02 and global0 = "Notification" ) + ( this = "mediaCapabilities" and weight = 124.39 and global0 = "navigator" ) or - ( this = "getBattery" and weight = 114.16 and global0 = "navigator" ) + ( this = "permissions" and weight = 70.22 and global0 = "navigator" ) or - ( this = "webkitPersistentStorage" and weight = 150.79 and global0 = "navigator" ) + ( this = "webkitPersistentStorage" and weight = 113.71 and global0 = "navigator" ) or - ( this = "requestMediaKeySystemAccess" and weight = 17.34 and global0 = "navigator" ) + ( this = "requestMediaKeySystemAccess" and weight = 16.88 and global0 = "navigator" ) or - ( this = "getGamepads" and weight = 235.72 and global0 = "navigator" ) + ( this = "getGamepads" and weight = 202.54 and global0 = "navigator" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll b/.github/codeql/queries/autogen_fpGlobalObjectProperty1.qll index 4017e4e871d..b874d860835 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 = 301.74 and global0 = "navigator" and global1 = "mediaDevices" ) + ( this = "enumerateDevices" and weight = 329.65 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 181c833165f..df96f92eaed 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 = 5043.14 and global0 = "Gyroscope" ) + ( this = "x" and weight = 7033.93 and global0 = "Gyroscope" ) or - ( this = "y" and weight = 5043.14 and global0 = "Gyroscope" ) + ( this = "y" and weight = 7033.93 and global0 = "Gyroscope" ) or - ( this = "z" and weight = 5043.14 and global0 = "Gyroscope" ) + ( this = "z" and weight = 7033.93 and global0 = "Gyroscope" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll b/.github/codeql/queries/autogen_fpGlobalTypeProperty1.qll index 31d9d9808f7..dbdf06d0d47 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 = 17.99 and global0 = "Intl" and global1 = "DateTimeFormat" ) + ( this = "resolvedOptions" and weight = 18.83 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 1997bc1687f..7a337b3519d 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 = 19.42 ) + ( this = "devicePixelRatio" and weight = 18.91 ) or - ( this = "screenX" and weight = 319.5 ) + ( this = "screenX" and weight = 355.18 ) or - ( this = "screenY" and weight = 303.5 ) + ( this = "screenY" and weight = 309.2 ) or - ( this = "outerWidth" and weight = 102.66 ) + ( this = "outerWidth" and weight = 109.86 ) or - ( this = "outerHeight" and weight = 183.94 ) + ( this = "outerHeight" and weight = 178.05 ) or - ( this = "screenLeft" and weight = 315.55 ) + ( this = "screenLeft" and weight = 374.27 ) or - ( this = "screenTop" and weight = 313.8 ) + ( this = "screenTop" and weight = 373.73 ) or - ( this = "indexedDB" and weight = 17.79 ) + ( this = "indexedDB" and weight = 18.81 ) or - ( this = "openDatabase" and weight = 143.97 ) + ( this = "openDatabase" and weight = 134.7 ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpRenderingContextProperty.qll b/.github/codeql/queries/autogen_fpRenderingContextProperty.qll index 13574653e50..510e393b984 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 = "getExtension" and weight = 20.11 and contextType = "webgl" ) + ( this = "getExtension" and weight = 17.76 and contextType = "webgl" ) or - ( this = "getParameter" and weight = 22.92 and contextType = "webgl" ) + ( this = "getParameter" and weight = 20.31 and contextType = "webgl" ) or - ( this = "getImageData" and weight = 40.74 and contextType = "2d" ) + ( this = "getParameter" and weight = 65.17 and contextType = "webgl2" ) or - ( this = "getParameter" and weight = 41.44 and contextType = "webgl2" ) + ( this = "getShaderPrecisionFormat" and weight = 107.03 and contextType = "webgl2" ) or - ( this = "getShaderPrecisionFormat" and weight = 108.95 and contextType = "webgl2" ) + ( this = "getExtension" and weight = 70.03 and contextType = "webgl2" ) or - ( this = "getExtension" and weight = 44.59 and contextType = "webgl2" ) + ( this = "getContextAttributes" and weight = 175.38 and contextType = "webgl2" ) or - ( this = "getContextAttributes" and weight = 187.09 and contextType = "webgl2" ) + ( this = "getSupportedExtensions" and weight = 487.31 and contextType = "webgl2" ) or - ( this = "getSupportedExtensions" and weight = 535.91 and contextType = "webgl2" ) + ( this = "getImageData" and weight = 44.3 and contextType = "2d" ) or - ( this = "measureText" and weight = 45.5 and contextType = "2d" ) + ( this = "measureText" and weight = 47.23 and contextType = "2d" ) or - ( this = "getShaderPrecisionFormat" and weight = 632.69 and contextType = "webgl" ) + ( this = "getShaderPrecisionFormat" and weight = 595.72 and contextType = "webgl" ) or - ( this = "getContextAttributes" and weight = 1404.12 and contextType = "webgl" ) + ( this = "getContextAttributes" and weight = 1038.26 and contextType = "webgl" ) or - ( this = "getSupportedExtensions" and weight = 968.57 and contextType = "webgl" ) + ( this = "getSupportedExtensions" and weight = 805.83 and contextType = "webgl" ) or - ( this = "readPixels" and weight = 21.25 and contextType = "webgl" ) + ( this = "readPixels" and weight = 20.6 and contextType = "webgl" ) or - ( this = "isPointInPath" and weight = 5043.14 and contextType = "2d" ) + ( this = "isPointInPath" and weight = 7033.93 and contextType = "2d" ) or - ( this = "readPixels" and weight = 68.7 and contextType = "webgl2" ) + ( this = "readPixels" and weight = 73.62 and contextType = "webgl2" ) } float getWeight() { diff --git a/.github/codeql/queries/autogen_fpSensorProperty.qll b/.github/codeql/queries/autogen_fpSensorProperty.qll index 2a50880a6e9..776a78d8434 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 = 123.75 ) + ( this = "start" and weight = 104.06 ) } float getWeight() { diff --git a/metadata/modules.json b/metadata/modules.json index f1258462fee..a08bfd3b4ac 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -1709,6 +1709,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "clickio", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "codefuel", @@ -3340,6 +3347,13 @@ "gvlid": 1485, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "ybidder", + "aliasOf": "nexx360", + "gvlid": 1253, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "nobid", @@ -3690,6 +3704,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "publicgood", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "publir", @@ -4236,6 +4257,20 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "amcom", + "aliasOf": "smarthub", + "gvlid": null, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "adastra", + "aliasOf": "smarthub", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "smartico", @@ -4495,6 +4530,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "topon", + "aliasOf": null, + "gvlid": 1305, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "tpmn", @@ -5248,7 +5290,7 @@ { "componentType": "rtd", "componentName": "permutive", - "gvlid": null, + "gvlid": 361, "disclosureURL": null }, { @@ -5653,7 +5695,7 @@ { "componentType": "userId", "componentName": "permutiveIdentityManagerId", - "gvlid": null, + "gvlid": 361, "disclosureURL": null, "aliasOf": null }, @@ -6092,4 +6134,4 @@ "gvlid": null } ] -} +} \ No newline at end of file diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 05ea618fdee..d532327e06e 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": "2025-11-10T11:41:10.873Z", + "timestamp": "2025-11-19T20:51:08.096Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 8d13cbe209f..50f8e7b1997 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": "2025-11-10T11:41:10.975Z", + "timestamp": "2025-11-19T20:51:08.188Z", "disclosures": [] } }, diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index c071fc720e2..39f13157d79 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": "2025-11-10T11:41:10.977Z", + "timestamp": "2025-11-19T20:51:08.191Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index 906df47afad..4ef5ccfc5ae 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": "2025-11-10T11:41:11.014Z", + "timestamp": "2025-11-19T20:51:08.247Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index e3c9b439e92..5d130b541fb 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": "2025-11-10T11:41:11.073Z", + "timestamp": "2025-11-19T20:51:08.295Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index 27ebdb1565f..ff4d88380d3 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": "2025-11-10T11:41:11.073Z", + "timestamp": "2025-11-19T20:51:08.295Z", "disclosures": [] } }, diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index 2a2de2b086c..5732298509a 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": "2025-11-10T11:41:11.231Z", + "timestamp": "2025-11-19T20:51:08.584Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index 4d5867af9d2..e18260a2a5d 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": "2025-11-10T11:41:11.878Z", + "timestamp": "2025-11-19T20:51:09.232Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 5ab5cdbffd2..00a4a50710f 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": "2025-11-10T11:41:11.908Z", + "timestamp": "2025-11-19T20:51:09.232Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index a6ab9d0f94b..8b35bf5d541 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": "2025-11-10T11:41:12.255Z", + "timestamp": "2025-11-19T20:51:09.608Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 4412e00c463..a106bd54e64 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": "2025-11-10T11:41:12.517Z", + "timestamp": "2025-11-19T20:51:09.872Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index f377f494e85..2e3a6eba0b5 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": "2025-11-10T11:41:12.640Z", + "timestamp": "2025-11-19T20:51:10.019Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index e6f5a7c877b..e073819755d 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": "2025-11-10T11:41:12.669Z", + "timestamp": "2025-11-19T20:51:10.063Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,15 +17,15 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:12.669Z", + "timestamp": "2025-11-19T20:51:10.063Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2025-11-10T11:41:12.711Z", + "timestamp": "2025-11-19T20:51:10.114Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:13.448Z", + "timestamp": "2025-11-19T20:51:10.823Z", "disclosures": [] } }, diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index a6c3063ecba..49230562aff 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": "2025-11-10T11:41:13.921Z", + "timestamp": "2025-11-19T20:51:11.408Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:13.576Z", + "timestamp": "2025-11-19T20:51:10.948Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index 3c693dfc5a6..37962a173e4 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": "2025-11-10T11:41:13.922Z", + "timestamp": "2025-11-19T20:51:11.409Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index 402a63412bf..cedbe489a6d 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": "2025-11-10T11:41:14.298Z", + "timestamp": "2025-11-19T20:51:11.787Z", "disclosures": [] } }, diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index 0e1390ad14f..21d4b63fd47 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": "2025-11-10T11:41:14.298Z", + "timestamp": "2025-11-19T20:51:11.787Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index 7de3b1ae7d7..8137782114a 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": "2025-11-10T11:41:14.530Z", + "timestamp": "2025-11-19T20:51:12.039Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index e8a18066eaf..7cbe053afbd 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": "2025-11-10T11:41:14.862Z", + "timestamp": "2025-11-19T20:51:12.355Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index 264db2c36fb..4583bae817b 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": "2025-11-10T11:41:14.863Z", + "timestamp": "2025-11-19T20:51:12.355Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index 6e9378657da..7e729f814b7 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": "2025-11-10T11:41:14.891Z", + "timestamp": "2025-11-19T20:51:12.400Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index c8467b15b94..567fc0e4041 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": "2025-11-10T11:41:14.919Z", + "timestamp": "2025-11-19T20:51:12.421Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index c78547386a8..d8b870afe6b 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": "2025-11-10T11:41:15.250Z", + "timestamp": "2025-11-19T20:51:12.756Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index ad1179ceb20..2e3a762ddcd 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": "2025-11-10T11:41:15.250Z", + "timestamp": "2025-11-19T20:51:12.757Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index f9646e522b1..fd47c760862 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": "2025-11-10T11:41:15.279Z", + "timestamp": "2025-11-19T20:51:12.854Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index f063da51be4..4b559c80cdc 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": "2025-11-10T11:41:15.577Z", + "timestamp": "2025-11-19T20:51:13.161Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index 40c95ac8807..b3fb4a5db30 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": "2025-11-10T11:41:15.578Z", + "timestamp": "2025-11-19T20:51:13.161Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { - "timestamp": "2025-11-10T11:41:15.595Z", + "timestamp": "2025-11-19T20:51:13.180Z", "disclosures": [ { "identifier": "waterFallCacheAnsKey_*", @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2025-11-10T11:41:15.730Z", + "timestamp": "2025-11-19T20:51:13.354Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index 8f7a2aec1cd..e62d3831e88 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": "2025-11-10T11:41:15.779Z", + "timestamp": "2025-11-19T20:51:13.523Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index 3ec7a4151ca..ac02403710e 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": "2025-11-10T11:41:15.780Z", + "timestamp": "2025-11-19T20:51:13.524Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index 0b028e27ade..b4066e9b8ef 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": "2025-11-10T11:41:15.797Z", + "timestamp": "2025-11-19T20:51:13.552Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index f4cdee236ff..46c58dddb5e 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": "2025-11-10T11:41:16.258Z", + "timestamp": "2025-11-19T20:51:14.020Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index b0fa3c1bbf7..ad298eddabb 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": "2025-11-10T11:41:16.285Z", + "timestamp": "2025-11-19T20:51:14.058Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index 4273603a956..0494a5ea70b 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": "2025-11-10T11:41:16.574Z", + "timestamp": "2025-11-19T20:51:14.354Z", "disclosures": [] } }, diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index f8c2ed5427f..77076a0455c 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": "2025-11-10T11:41:16.692Z", + "timestamp": "2025-11-19T20:51:14.408Z", "disclosures": [] } }, diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index 5d854db440b..fdc85537934 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": "2025-11-10T11:41:16.693Z", + "timestamp": "2025-11-19T20:51:14.408Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 3943f391686..827805b370f 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": "2025-11-10T11:41:17.033Z", + "timestamp": "2025-11-19T20:51:14.519Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index d251299601b..6dd538cd5bf 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": "2025-11-10T11:41:17.051Z", + "timestamp": "2025-11-19T20:51:14.636Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index de538718dc9..831bda55c41 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": "2025-11-10T11:41:17.074Z", + "timestamp": "2025-11-19T20:51:14.690Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index 5c5adf4e0d0..99a201651de 100644 --- a/metadata/modules/appnexusBidAdapter.json +++ b/metadata/modules/appnexusBidAdapter.json @@ -2,23 +2,23 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2025-11-10T11:41:17.879Z", + "timestamp": "2025-11-19T20:51:15.356Z", "disclosures": [] }, "https://tcf.emetriq.de/deviceStorageDisclosure.json": { - "timestamp": "2025-11-10T11:41:17.182Z", + "timestamp": "2025-11-19T20:51:14.828Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:17.204Z", + "timestamp": "2025-11-19T20:51:14.849Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2025-11-10T11:41:17.429Z", + "timestamp": "2025-11-19T20:51:14.865Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2025-11-10T11:41:17.879Z", + "timestamp": "2025-11-19T20:51:15.356Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index 4ea33ee1005..c0935899043 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": "2025-11-10T11:41:17.906Z", + "timestamp": "2025-11-19T20:51:15.414Z", "disclosures": [] } }, diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index 3958ccce654..86ba9c1dc17 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": "2025-11-10T11:41:17.970Z", + "timestamp": "2025-11-19T20:51:15.528Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index 4996580fa8d..d308af1c05b 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": "2025-11-10T11:41:17.990Z", + "timestamp": "2025-11-19T20:51:15.551Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index f22cefed7c6..119c83c1d1a 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": "2025-11-10T11:41:18.038Z", + "timestamp": "2025-11-19T20:51:15.615Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index bfd34532f14..9dacb4aca40 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": "2025-11-10T11:41:18.076Z", + "timestamp": "2025-11-19T20:51:15.658Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index f7d9015cc0a..63d6f32a7c8 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": "2025-11-10T11:41:18.097Z", + "timestamp": "2025-11-19T20:51:15.684Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index df982ea319c..3fd5c0a4a8d 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": "2025-11-10T11:41:18.465Z", + "timestamp": "2025-11-19T20:51:16.048Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index 5e19632907b..36747536871 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": "2025-11-10T11:41:18.566Z", + "timestamp": "2025-11-19T20:51:16.175Z", "disclosures": [] } }, diff --git a/metadata/modules/bidfuseBidAdapter.json b/metadata/modules/bidfuseBidAdapter.json index 50707a0167f..0f1ca2fa3fa 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": "2025-11-10T11:41:18.621Z", + "timestamp": "2025-11-19T20:51:16.229Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index b27b3e07e76..465dd2cbdfe 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": "2025-11-10T11:41:18.804Z", + "timestamp": "2025-11-19T20:51:16.409Z", "disclosures": [] } }, diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index 63983a136e9..371cf75c451 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": "2025-11-10T11:41:18.841Z", + "timestamp": "2025-11-19T20:51:16.457Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index ae80661cb96..e04e63b647b 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": "2025-11-10T11:41:19.153Z", + "timestamp": "2025-11-19T20:51:16.779Z", "disclosures": [] } }, diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index 0e5ca4a4b88..a0de664de61 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": "2025-11-10T11:41:19.703Z", + "timestamp": "2025-11-19T20:51:17.219Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index 220d3ded386..c3f5aae4563 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": "2025-11-10T11:41:20.048Z", + "timestamp": "2025-11-19T20:51:17.572Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index c9c9f02f877..75dd1c781be 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": "2025-11-10T11:41:20.069Z", + "timestamp": "2025-11-19T20:51:17.595Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 7ab0f21eb7b..597e7796973 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": "2025-11-10T11:41:20.090Z", + "timestamp": "2025-11-19T20:51:17.700Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index 104ecacb74c..d81cca591e1 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": "2025-11-10T11:41:20.230Z", + "timestamp": "2025-11-19T20:51:17.840Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index 08f016dbbe0..8d71736c557 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": "2025-11-10T11:41:20.278Z", + "timestamp": "2025-11-19T20:51:17.857Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index fbb539fea09..5c565ba1aa8 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": "2025-11-10T11:41:20.331Z", + "timestamp": "2025-11-19T20:51:17.919Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index 1121827cd76..921493ce0fb 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": "2025-11-10T11:41:10.870Z", + "timestamp": "2025-11-19T20:51:08.094Z", "disclosures": [ { "identifier": "iabToFwMappingkey", diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 101afaccdb7..307b7549665 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": "2025-11-10T11:41:20.510Z", + "timestamp": "2025-11-19T20:51:18.230Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index c37211d4647..16accf9cae2 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": "2025-11-10T11:41:20.842Z", + "timestamp": "2025-11-19T20:51:18.581Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json new file mode 100644 index 00000000000..de263bb0585 --- /dev/null +++ b/metadata/modules/clickioBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "clickio", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index 818aeccec74..925c8facc3f 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": "2025-11-10T11:41:20.844Z", + "timestamp": "2025-11-19T20:51:18.583Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index 82a4165c439..edbf991fc56 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": "2025-11-10T11:41:20.859Z", + "timestamp": "2025-11-19T20:51:18.599Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index a98da503920..9a4634d837c 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": "2025-11-10T11:41:20.878Z", + "timestamp": "2025-11-19T20:51:18.626Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index 71a575ea516..92943284539 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": "2025-11-10T11:41:20.946Z", + "timestamp": "2025-11-19T20:51:18.722Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index 5d4a28aa89f..474b3ab83eb 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": "2025-11-10T11:41:20.966Z", + "timestamp": "2025-11-19T20:51:18.745Z", "disclosures": [] } }, diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index e9fdd2b0dd4..5cab777a157 100644 --- a/metadata/modules/conversantBidAdapter.json +++ b/metadata/modules/conversantBidAdapter.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.1/device_storage_disclosure.json": { - "timestamp": "2025-11-10T11:41:21.759Z", + "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json": { + "timestamp": "2025-11-19T20:51:18.822Z", "disclosures": [ { "identifier": "dtm_status", @@ -554,6 +554,25 @@ 10, 11 ] + }, + { + "identifier": "hConversionEventId", + "type": "cookie", + "maxAgeSeconds": 2592000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] } ] } @@ -564,7 +583,7 @@ "componentName": "conversant", "aliasOf": null, "gvlid": 24, - "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.1/device_storage_disclosure.json" + "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json" }, { "componentType": "bidder", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 70692327512..164f7677af4 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": "2025-11-10T11:41:21.777Z", + "timestamp": "2025-11-19T20:51:18.845Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index b773d5a3c8d..9b49d8dfb84 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": "2025-11-10T11:41:21.865Z", + "timestamp": "2025-11-19T20:51:18.971Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index e1cc053fbaa..c405acba13c 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": { - "timestamp": "2025-11-10T11:41:21.908Z", + "timestamp": "2025-11-19T20:51:19.021Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index e463e89f3ee..cf3766536e9 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": { - "timestamp": "2025-11-10T11:41:21.930Z", + "timestamp": "2025-11-19T20:51:19.041Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index d2437db0a0b..fb9e068e4e3 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": "2025-11-10T11:41:21.931Z", + "timestamp": "2025-11-19T20:51:19.041Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index e8b6b59b485..e4b953bfb59 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": "2025-11-10T11:41:22.260Z", + "timestamp": "2025-11-19T20:51:19.388Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index 0be58872c96..c48795f0a1e 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": "2025-11-10T11:41:24.173Z", + "timestamp": "2025-11-19T20:51:19.691Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index 5165051f5e6..00eed3703dd 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": "2025-11-10T11:41:10.869Z", + "timestamp": "2025-11-19T20:51:08.093Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index 2f6b7aa0581..8b38c30277a 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": "2025-11-10T11:41:24.267Z", + "timestamp": "2025-11-19T20:51:19.711Z", "disclosures": [] } }, diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index 170201aaa23..95a5a4ccfc6 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": "2025-11-10T11:41:24.364Z", + "timestamp": "2025-11-19T20:51:20.094Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index 0fade06c854..02545785549 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": "2025-11-10T11:41:24.769Z", + "timestamp": "2025-11-19T20:51:20.507Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index a8a62254cba..7131f0c4f2c 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": "2025-11-10T11:41:24.833Z", + "timestamp": "2025-11-19T20:51:20.947Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 38a1f678bdf..b3999a2f058 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": "2025-11-10T11:41:24.834Z", + "timestamp": "2025-11-19T20:51:20.947Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index bc880da4280..f147261d7b4 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": "2025-11-10T11:41:25.200Z", + "timestamp": "2025-11-19T20:51:21.331Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index 913c3357893..11b3ddc6a64 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": "2025-11-10T11:41:25.215Z", + "timestamp": "2025-11-19T20:51:21.358Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index 99841e87e21..d120fbe6044 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": "2025-11-10T11:41:25.967Z", + "timestamp": "2025-11-19T20:51:22.114Z", "disclosures": [] } }, diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 976f2a0c178..fa0a5901037 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": "2025-11-10T11:41:25.968Z", + "timestamp": "2025-11-19T20:51:22.115Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 071e64c0c9e..e4c50af4c11 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": "2025-11-10T11:41:26.620Z", + "timestamp": "2025-11-19T20:51:22.794Z", "disclosures": null } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index b44dfdece4d..b73461bb162 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": "2025-11-10T11:41:26.928Z", + "timestamp": "2025-11-19T20:51:23.104Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index fdc8da20c3d..03faa868057 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": "2025-11-10T11:41:26.970Z", + "timestamp": "2025-11-19T20:51:23.177Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index 195b1b775e3..ac701e3462c 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": "2025-11-10T11:41:26.993Z", + "timestamp": "2025-11-19T20:51:23.210Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index f9043e75efc..f3f5575ceeb 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": "2025-11-10T11:41:27.023Z", + "timestamp": "2025-11-19T20:51:23.853Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index 044cf8516d8..eb2ef4ed708 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": "2025-11-10T11:41:27.042Z", + "timestamp": "2025-11-19T20:51:23.873Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 12ad2d0df71..430d3c77873 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": "2025-11-10T11:41:27.617Z", + "timestamp": "2025-11-19T20:51:24.550Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 7fc5c12d5a9..b93abf1f5aa 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": "2025-11-10T11:41:27.849Z", + "timestamp": "2025-11-19T20:51:24.771Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index 90b53a0c5f6..6385f9ed012 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": "2025-11-10T11:41:28.014Z", + "timestamp": "2025-11-19T20:51:24.973Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index 498eefda557..5a38f86edb4 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": "2025-11-10T11:41:28.153Z", + "timestamp": "2025-11-19T20:51:25.329Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index 29e4a1d0a94..b72069e034e 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": "2025-11-10T11:41:28.242Z", + "timestamp": "2025-11-19T20:51:25.921Z", "disclosures": [] } }, diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index 9fb71ca2df9..fb4dfba197d 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": "2025-11-10T11:41:28.507Z", + "timestamp": "2025-11-19T20:51:26.097Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 059c04ff011..5df2c608934 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": "2025-11-10T11:41:28.946Z", + "timestamp": "2025-11-19T20:51:26.671Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 7124d6bf67f..1104c937c2d 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": "2025-11-10T11:41:28.965Z", + "timestamp": "2025-11-19T20:51:26.704Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index 92d76383cdd..b3866edd805 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": "2025-11-10T11:41:29.081Z", + "timestamp": "2025-11-19T20:51:26.766Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index 868301bc3f3..5067cac4039 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": "2025-11-10T11:41:29.232Z", + "timestamp": "2025-11-19T20:51:26.916Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index 1a737f16ce4..5930b2bfbd5 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": "2025-11-10T11:41:29.297Z", + "timestamp": "2025-11-19T20:51:26.988Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index 3fb00e9192e..9d4a1d5dfd2 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": "2025-11-10T11:41:29.445Z", + "timestamp": "2025-11-19T20:51:27.109Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index a6b3edb8e19..24eacb9bc79 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": "2025-11-10T11:41:29.445Z", + "timestamp": "2025-11-19T20:51:27.109Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index aec5ee521ae..29aab4a9ff7 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": "2025-11-10T11:41:29.711Z", + "timestamp": "2025-11-19T20:51:27.382Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index 6321ecb8927..bbc919ef861 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": "2025-11-10T11:41:29.866Z", + "timestamp": "2025-11-19T20:51:27.667Z", "disclosures": [] } }, diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index 8ffc8facede..1e959d69d9b 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": "2025-11-10T11:41:30.175Z", + "timestamp": "2025-11-19T20:51:27.944Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index f4fe982f1b2..f75633254dd 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": "2025-11-10T11:41:30.195Z", + "timestamp": "2025-11-19T20:51:27.970Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index f848e17d7b2..de6ab2e3d5e 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": "2025-11-10T11:41:30.557Z", + "timestamp": "2025-11-19T20:51:28.316Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 8125c15088a..7da0a1c40e4 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": "2025-11-10T11:41:30.908Z", + "timestamp": "2025-11-19T20:51:28.592Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index 46f8a0bfce3..bade3996e48 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": "2025-11-10T11:41:30.908Z", + "timestamp": "2025-11-19T20:51:28.593Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index dcfa27640b4..9678d9143ca 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": "2025-11-10T11:41:30.939Z", + "timestamp": "2025-11-19T20:51:28.624Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 7205568930a..7dfe9d8f578 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": "2025-11-10T11:41:30.977Z", + "timestamp": "2025-11-19T20:51:28.651Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index 0e2e07daf1d..7ea4211bf8e 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": "2025-11-10T11:41:31.029Z", + "timestamp": "2025-11-19T20:51:28.710Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 769c49d3467..cfd02b7360c 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": "2025-11-10T11:41:31.367Z", + "timestamp": "2025-11-19T20:51:29.106Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index 9443e8792b5..b45469e19ec 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": "2025-11-10T11:41:31.933Z", + "timestamp": "2025-11-19T20:51:29.586Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index 3d7154320a1..8526c632c84 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": "2025-11-10T11:41:32.110Z", + "timestamp": "2025-11-19T20:51:29.770Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index ee71cf8b7ec..c7e37ae5747 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": "2025-11-10T11:41:32.583Z", + "timestamp": "2025-11-19T20:51:30.293Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 4ad0327dd16..03601c43bf2 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": "2025-11-10T11:41:32.610Z", + "timestamp": "2025-11-19T20:51:30.311Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index 922c986ea68..dc67cb78e6d 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": "2025-11-10T11:41:32.808Z", + "timestamp": "2025-11-19T20:51:30.882Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index f9b8a4048ea..7adb8674f49 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": "2025-11-10T11:41:32.830Z", + "timestamp": "2025-11-19T20:51:30.901Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index 5531e1fbfb0..659ce95ef58 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": "2025-11-10T11:41:32.862Z", + "timestamp": "2025-11-19T20:51:30.975Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2025-11-10T11:41:32.918Z", + "timestamp": "2025-11-19T20:51:31.006Z", "disclosures": [] } }, diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index c02fb7130e4..6239d4a31e8 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": "2025-11-10T11:41:32.919Z", + "timestamp": "2025-11-19T20:51:31.007Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index d47673113cf..6ff7475fe19 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": "2025-11-10T11:41:32.934Z", + "timestamp": "2025-11-19T20:51:31.046Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 015d56e0e41..00b970f6826 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": "2025-11-10T11:41:32.935Z", + "timestamp": "2025-11-19T20:51:31.047Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index ba28fc75e84..219e66cd8b5 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": "2025-11-10T11:41:32.961Z", + "timestamp": "2025-11-19T20:51:31.069Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 2a67b8a156e..46314251fc3 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": "2025-11-10T11:41:33.059Z", + "timestamp": "2025-11-19T20:51:31.214Z", "disclosures": [ { "identifier": "panoramaId", diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index a37937e5db2..797934db108 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": "2025-11-10T11:41:33.072Z", + "timestamp": "2025-11-19T20:51:31.229Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 6208c5458f5..760afedd2cc 100644 --- a/metadata/modules/madvertiseBidAdapter.json +++ b/metadata/modules/madvertiseBidAdapter.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://mobile.mng-ads.com/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:33.477Z", + "https://adserver.bluestack.app/deviceStorage.json": { + "timestamp": "2025-11-19T20:51:31.756Z", "disclosures": [] } }, @@ -12,7 +12,7 @@ "componentName": "madvertise", "aliasOf": null, "gvlid": 153, - "disclosureURL": "https://mobile.mng-ads.com/deviceStorage.json" + "disclosureURL": "https://adserver.bluestack.app/deviceStorage.json" } ] } \ No newline at end of file diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index 05800612bff..616c4e5f5cf 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": "2025-11-10T11:41:33.822Z", + "timestamp": "2025-11-19T20:51:32.115Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index 09f223a4d28..21f9a7ad631 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": "2025-11-10T11:41:33.942Z", + "timestamp": "2025-11-19T20:51:32.236Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index e3f07de5ac6..a5d933f8cc5 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": "2025-11-10T11:41:34.076Z", + "timestamp": "2025-11-19T20:51:32.366Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index b2ba1b7a26e..fbe6c0b1f36 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": "2025-11-10T11:41:34.094Z", + "timestamp": "2025-11-19T20:51:32.385Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index e2eaed9c22c..11856c9c3c2 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": "2025-11-10T11:41:34.094Z", + "timestamp": "2025-11-19T20:51:32.386Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index 55c10e96d76..c1866cdc1f0 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": "2025-11-10T11:41:34.195Z", + "timestamp": "2025-11-19T20:51:32.459Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index dd6e514609f..4765a26023d 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": "2025-10-31T17:47:02.069Z", + "timestamp": "2025-11-19T20:51:32.743Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:34.519Z", + "timestamp": "2025-11-19T20:51:32.879Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index a0bc8fa5610..3ab9de062b7 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": "2025-11-10T11:41:34.554Z", + "timestamp": "2025-11-19T20:51:32.917Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index 09aad00dbbd..11ba1aedc46 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": "2025-11-10T11:41:35.075Z", + "timestamp": "2025-11-19T20:51:33.449Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index 6bad4449997..b627ea6172a 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": "2025-11-10T11:41:35.175Z", + "timestamp": "2025-11-19T20:51:33.545Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index 646fa4474cf..729e83d291c 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": "2025-11-10T11:41:35.175Z", + "timestamp": "2025-11-19T20:51:33.545Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index dbcfcc91f22..98998cf1ad3 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": "2025-11-10T11:41:35.175Z", + "timestamp": "2025-11-19T20:51:33.546Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index 237dfc6c35e..4d433c3a2bf 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": "2025-11-10T11:41:35.192Z", + "timestamp": "2025-11-19T20:51:33.577Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index 29d9fbd6480..03233c61c19 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": "2025-11-10T11:41:35.247Z", + "timestamp": "2025-11-19T20:51:33.631Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index 3682c594f08..73706e77eb1 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": "2025-11-10T11:41:35.262Z", + "timestamp": "2025-11-19T20:51:33.651Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index 959dbad1ba6..3283be58b12 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": "2025-11-10T11:41:35.279Z", + "timestamp": "2025-11-19T20:51:33.671Z", "disclosures": [] } }, diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index d3e1eec7f2c..083a53a9935 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": "2025-11-10T11:41:35.280Z", + "timestamp": "2025-11-19T20:51:33.671Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index a7877ff3ab9..e4f1a7a04ed 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": "2025-11-10T11:41:35.281Z", + "timestamp": "2025-11-19T20:51:33.672Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index c5b513dc3d2..36f38526ff4 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": "2025-11-10T11:41:35.625Z", + "timestamp": "2025-11-19T20:51:34.024Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index d824160fc42..1d072cda0f9 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": "2025-11-10T11:41:35.649Z", + "timestamp": "2025-11-19T20:51:34.046Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index 1578563981a..f647c5dee6b 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": "2025-11-10T11:41:35.649Z", + "timestamp": "2025-11-19T20:51:34.047Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index f9d610f2ecd..e6a4502aa76 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": "2025-11-10T11:41:35.707Z", + "timestamp": "2025-11-19T20:51:34.118Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 9d9287dac73..902e839f5cd 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": "2025-11-10T11:41:35.910Z", + "timestamp": "2025-11-19T20:51:34.571Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2025-11-10T11:41:35.772Z", + "timestamp": "2025-11-19T20:51:34.414Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:35.794Z", + "timestamp": "2025-11-19T20:51:34.441Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:35.910Z", + "timestamp": "2025-11-19T20:51:34.571Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,11 +46,11 @@ ] }, "https://mediafuse.com/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:35.910Z", + "timestamp": "2025-11-19T20:51:34.571Z", "disclosures": [] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2025-11-10T11:41:35.989Z", + "timestamp": "2025-11-19T20:51:34.657Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -63,6 +63,10 @@ ] } ] + }, + "https://yieldbird.com/devicestorage.json": { + "timestamp": "2025-11-19T20:51:34.700Z", + "disclosures": [] } }, "components": [ @@ -184,6 +188,13 @@ "aliasOf": "nexx360", "gvlid": 1485, "disclosureURL": "https://gdpr.pubx.ai/devicestoragedisclosure.json" + }, + { + "componentType": "bidder", + "componentName": "ybidder", + "aliasOf": "nexx360", + "gvlid": 1253, + "disclosureURL": "https://yieldbird.com/devicestorage.json" } ] } \ No newline at end of file diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index cec52faf113..860d8b6a786 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": "2025-11-10T11:41:36.013Z", + "timestamp": "2025-11-19T20:51:35.069Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 4674e5efa3d..80e71b663ae 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": "2025-11-10T11:41:36.029Z", + "timestamp": "2025-11-19T20:51:35.089Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index 77eb8ef2b81..091a3406069 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": "2025-11-10T11:41:37.212Z", + "timestamp": "2025-11-19T20:51:36.315Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index 57c42400301..cf40ea10f04 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": "2025-11-10T11:41:37.545Z", + "timestamp": "2025-11-19T20:51:36.644Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index 6c4187335bf..9a67d3c863d 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": "2025-11-10T11:41:37.597Z", + "timestamp": "2025-11-19T20:51:36.709Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index 7b5cc5eee49..e802bd7f5fd 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": "2025-11-10T11:41:37.637Z", + "timestamp": "2025-11-19T20:51:36.804Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 08783d6ac2a..8db87fd9a54 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": "2025-11-10T11:41:37.638Z", + "timestamp": "2025-11-19T20:51:36.805Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index 3bd00fc710a..950b15d5a40 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": "2025-11-10T11:41:37.930Z", + "timestamp": "2025-11-19T20:51:37.157Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index e37eba3c4eb..bbaade2d23e 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": "2025-11-10T11:41:37.968Z", + "timestamp": "2025-11-19T20:51:37.191Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index fec62ac5fa1..311a6d714cb 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": "2025-11-10T11:41:37.996Z", + "timestamp": "2025-11-19T20:51:37.267Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index a82f6527f99..7258949c64c 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": "2025-11-10T11:41:38.012Z", + "timestamp": "2025-11-19T20:51:37.295Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index 108b7e5c33b..eed3e4d239c 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": "2025-11-10T11:41:38.068Z", + "timestamp": "2025-11-19T20:51:37.340Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index e91a209b091..b9df0f08646 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": "2025-11-10T11:41:38.322Z", + "timestamp": "2025-11-19T20:51:37.600Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index 49e8280d179..9f2cba4f046 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": "2025-11-10T11:41:38.597Z", + "timestamp": "2025-11-19T20:51:37.889Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index 290b550797a..e5de08e8dfd 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": "2025-11-10T11:41:38.800Z", + "timestamp": "2025-11-19T20:51:38.077Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index ca0f34de279..fbfb0d29fc8 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": "2025-11-10T11:41:38.960Z", + "timestamp": "2025-11-19T20:51:38.338Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index 64c9b50f6ed..4a45428b467 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": "2025-11-10T11:41:38.991Z", + "timestamp": "2025-11-19T20:51:38.364Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index ce7b56fecbc..e3b82502b76 100644 --- a/metadata/modules/permutiveIdentityManagerIdSystem.json +++ b/metadata/modules/permutiveIdentityManagerIdSystem.json @@ -1,13 +1,286 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, + "disclosures": { + "https://assets.permutive.app/tcf/tcf.json": { + "timestamp": "2025-11-19T20:51:38.784Z", + "disclosures": [ + { + "identifier": "_pdfps", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-models", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-queries", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_prubicons", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_psegs", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pclmc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pnativo", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-pvc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-enrichers", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-session", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-misc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-id", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_psmart", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_paols", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_papns", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pcrdbs", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pcrprs", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pdem-state", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pfwqp", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_ppam", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_prps", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-events-cache", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-loaded", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pclmc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-tpd", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-consent", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "__permutiveConfigQueryParams", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "events_*", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "keys_*", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-prebid-*", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-id", + "type": "cookie", + "maxAgeSeconds": 15770000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "_papns", + "type": "cookie", + "maxAgeSeconds": 15770000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pdfps", + "type": "cookie", + "maxAgeSeconds": 15770000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + } + ] + } + }, "components": [ { "componentType": "userId", "componentName": "permutiveIdentityManagerId", - "gvlid": null, - "disclosureURL": null, + "gvlid": 361, + "disclosureURL": "https://assets.permutive.app/tcf/tcf.json", "aliasOf": null } ] -} +} \ No newline at end of file diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 691d87b6af8..14fedd6d831 100644 --- a/metadata/modules/permutiveRtdProvider.json +++ b/metadata/modules/permutiveRtdProvider.json @@ -1,12 +1,285 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, + "disclosures": { + "https://assets.permutive.app/tcf/tcf.json": { + "timestamp": "2025-11-19T20:51:39.019Z", + "disclosures": [ + { + "identifier": "_pdfps", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-models", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-queries", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_prubicons", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_psegs", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pclmc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pnativo", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-pvc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-enrichers", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-session", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-misc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-id", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_psmart", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_paols", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_papns", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pcrdbs", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pcrprs", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pdem-state", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pfwqp", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_ppam", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_prps", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-events-cache", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-loaded", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pclmc", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-data-tpd", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-consent", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "__permutiveConfigQueryParams", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "events_*", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "keys_*", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-prebid-*", + "type": "web", + "maxAgeSeconds": null, + "purposes": [ + 1 + ] + }, + { + "identifier": "permutive-id", + "type": "cookie", + "maxAgeSeconds": 15770000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "_papns", + "type": "cookie", + "maxAgeSeconds": 15770000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + }, + { + "identifier": "_pdfps", + "type": "cookie", + "maxAgeSeconds": 15770000, + "cookieRefresh": false, + "purposes": [ + 1 + ] + } + ] + } + }, "components": [ { "componentType": "rtd", "componentName": "permutive", - "gvlid": null, - "disclosureURL": null + "gvlid": 361, + "disclosureURL": "https://assets.permutive.app/tcf/tcf.json" } ] -} +} \ No newline at end of file diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index 30c66bc2800..b37cd00a26b 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": "2025-11-10T11:41:39.392Z", + "timestamp": "2025-11-19T20:51:39.021Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index ed0b3a18556..60381299bfe 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": "2025-11-10T11:41:39.437Z", + "timestamp": "2025-11-19T20:51:39.074Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 43ccd4b3233..7fbdf54ee46 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": "2025-11-10T11:41:10.866Z", + "timestamp": "2025-11-19T20:51:08.091Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2025-11-10T11:41:10.868Z", + "timestamp": "2025-11-19T20:51:08.092Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index b9c2a45cb76..e05b75bd550 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": "2025-11-10T11:41:39.604Z", + "timestamp": "2025-11-19T20:51:39.262Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index 3486d61b3d0..b9873fe45c5 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": "2025-11-10T11:41:39.826Z", + "timestamp": "2025-11-19T20:51:39.489Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index 1c1646f6a3f..fcbe8780460 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": "2025-11-10T11:41:39.826Z", + "timestamp": "2025-11-19T20:51:39.490Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 6ef24660931..1744e2edd89 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": "2025-11-10T11:41:39.874Z", + "timestamp": "2025-11-19T20:51:39.534Z", "disclosures": [] } }, diff --git a/metadata/modules/publicGoodBidAdapter.json b/metadata/modules/publicGoodBidAdapter.json new file mode 100644 index 00000000000..a492629c705 --- /dev/null +++ b/metadata/modules/publicGoodBidAdapter.json @@ -0,0 +1,13 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": {}, + "components": [ + { + "componentType": "bidder", + "componentName": "publicgood", + "aliasOf": null, + "gvlid": null, + "disclosureURL": null + } + ] +} \ No newline at end of file diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index de86e2023ec..4c7748afbf2 100644 --- a/metadata/modules/publinkIdSystem.json +++ b/metadata/modules/publinkIdSystem.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.1/device_storage_disclosure.json": { - "timestamp": "2025-11-10T11:41:40.414Z", + "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json": { + "timestamp": "2025-11-19T20:51:39.911Z", "disclosures": [ { "identifier": "dtm_status", @@ -554,6 +554,25 @@ 10, 11 ] + }, + { + "identifier": "hConversionEventId", + "type": "cookie", + "maxAgeSeconds": 2592000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] } ] } @@ -563,7 +582,7 @@ "componentType": "userId", "componentName": "publinkId", "gvlid": 24, - "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.1/device_storage_disclosure.json", + "disclosureURL": "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.8/device_storage_disclosure.json", "aliasOf": null } ] diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index 27509eff221..2884bce73ef 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": "2025-11-10T11:41:40.415Z", + "timestamp": "2025-11-19T20:51:39.912Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index dd65259b412..7883f89271a 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": "2025-11-10T11:41:40.430Z", + "timestamp": "2025-11-19T20:51:39.955Z", "disclosures": [] } }, diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index 40728b0127a..0fcc3d50066 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": "2025-11-10T11:41:40.431Z", + "timestamp": "2025-11-19T20:51:39.957Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index dc67042d79b..52da89d65fe 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": "2025-11-10T11:41:40.447Z", + "timestamp": "2025-11-19T20:51:39.976Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json index 8a2449ad5b3..af2c2e18ed2 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": "2025-11-10T11:41:40.733Z", + "timestamp": "2025-11-19T20:51:40.184Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index d5bab440210..d01e396cf85 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": "2025-11-10T11:41:40.733Z", + "timestamp": "2025-11-19T20:51:40.185Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index 0eebde88696..84a63dfce45 100644 --- a/metadata/modules/readpeakBidAdapter.json +++ b/metadata/modules/readpeakBidAdapter.json @@ -2,8 +2,26 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.readpeak.com/tcf/deviceStorage.json": { - "timestamp": "2025-11-10T11:41:41.447Z", - "disclosures": [] + "timestamp": "2025-11-19T20:51:40.573Z", + "disclosures": [ + { + "identifier": "rp_uidfp", + "type": "cookie", + "maxAgeSeconds": 33696000, + "cookieRefresh": true, + "purposes": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 10 + ] + } + ] } }, "components": [ diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index edc54bb598e..e7400919ba9 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": "2025-11-10T11:41:41.468Z", + "timestamp": "2025-11-19T20:51:40.603Z", "disclosures": [] } }, diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index dcdb649ab70..c6676bc9a18 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": "2025-11-10T11:41:41.518Z", + "timestamp": "2025-11-19T20:51:40.699Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index b791acf4b35..320913ab3d3 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": "2025-11-10T11:41:41.673Z", + "timestamp": "2025-11-19T20:51:40.878Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index 1d55a0d2a28..9fa482f8c47 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": "2025-11-10T11:41:41.715Z", + "timestamp": "2025-11-19T20:51:40.922Z", "disclosures": [] } }, diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index c2a349520d1..c85e30b1da9 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": "2025-11-10T11:41:41.728Z", + "timestamp": "2025-11-19T20:51:40.952Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index dfe1d139f00..8a309ace5cf 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": "2025-11-10T11:41:41.756Z", + "timestamp": "2025-11-19T20:51:40.980Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index bb6e6192985..4b9eafb1589 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": "2025-11-10T11:41:41.990Z", + "timestamp": "2025-11-19T20:51:41.216Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index aa4aff087d3..e83029082bb 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": "2025-11-10T11:41:42.071Z", + "timestamp": "2025-11-19T20:51:41.284Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2025-11-10T11:41:42.071Z", + "timestamp": "2025-11-19T20:51:41.284Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 572d28eaee4..db0ff171189 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": "2025-11-10T11:41:42.072Z", + "timestamp": "2025-11-19T20:51:41.285Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index dd4d0abfd72..4af03548531 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": "2025-11-10T11:41:42.091Z", + "timestamp": "2025-11-19T20:51:41.308Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index fc5bfafb4f4..4c0a7bea043 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": "2025-11-10T11:41:42.297Z", + "timestamp": "2025-11-19T20:51:41.772Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index e31b7701185..435a1614543 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": "2025-11-10T11:41:42.546Z", + "timestamp": "2025-11-19T20:51:42.056Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index fd9590502cc..534b6c896fc 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": "2025-11-10T11:41:42.564Z", + "timestamp": "2025-11-19T20:51:42.072Z", "disclosures": null } }, diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index 3a18152d93b..fd098334269 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": "2025-11-10T11:41:45.154Z", + "timestamp": "2025-11-19T20:51:44.670Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index f5be8b79668..af146802a6b 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": "2025-11-10T11:41:45.174Z", + "timestamp": "2025-11-19T20:51:44.716Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index 24dfa79011a..9f3260038dd 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": "2025-11-10T11:41:45.175Z", + "timestamp": "2025-11-19T20:51:44.725Z", "disclosures": [] } }, diff --git a/metadata/modules/setupadBidAdapter.json b/metadata/modules/setupadBidAdapter.json index 2cb10596e42..64902e02316 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": "2025-11-10T11:41:45.221Z", + "timestamp": "2025-11-19T20:51:44.797Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index d1046874f78..37262a8bb45 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": "2025-11-10T11:41:45.322Z", + "timestamp": "2025-11-19T20:51:44.964Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index ca116283654..103686eec7f 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": "2025-11-10T11:41:45.458Z", + "timestamp": "2025-11-19T20:51:45.132Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index 5953d792d2b..10ae75097d0 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": "2025-11-10T11:41:45.458Z", + "timestamp": "2025-11-19T20:51:45.139Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index cea8077208e..a42de2663f7 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": "2025-11-10T11:41:45.481Z", + "timestamp": "2025-11-19T20:51:45.221Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index 7fbe88aac05..d5a8d366403 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": "2025-11-10T11:41:45.920Z", + "timestamp": "2025-11-19T20:51:45.652Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index 3c41a87e309..a0000813db7 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": "2025-11-10T11:41:45.937Z", + "timestamp": "2025-11-19T20:51:45.668Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index 27146fff19f..f78f52100c4 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": "2025-11-10T11:41:46.266Z", + "timestamp": "2025-11-19T20:51:45.988Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index f117814437b..40f3a1a6e2d 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": "2025-11-10T11:41:46.317Z", + "timestamp": "2025-11-19T20:51:46.084Z", "disclosures": [] } }, diff --git a/metadata/modules/smarthubBidAdapter.json b/metadata/modules/smarthubBidAdapter.json index 30d325335f5..eb77b60b1e7 100644 --- a/metadata/modules/smarthubBidAdapter.json +++ b/metadata/modules/smarthubBidAdapter.json @@ -78,6 +78,20 @@ "aliasOf": "smarthub", "gvlid": null, "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "amcom", + "aliasOf": "smarthub", + "gvlid": null, + "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "adastra", + "aliasOf": "smarthub", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 07fa1a17ab7..fd7eabf9d7c 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": "2025-11-10T11:41:46.319Z", + "timestamp": "2025-11-19T20:51:46.085Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index b9e48773cbd..20ba7fece8e 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": "2025-11-10T11:41:46.336Z", + "timestamp": "2025-11-19T20:51:46.102Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index cc747478779..bba81b93490 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": "2025-11-10T11:41:46.376Z", + "timestamp": "2025-11-19T20:51:46.145Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index 3b61b7c838d..13a8f3b522e 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": "2025-11-10T11:41:46.841Z", + "timestamp": "2025-11-19T20:51:46.631Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index 60ea9f904a8..9972c24ccc8 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": "2025-11-10T11:41:46.872Z", + "timestamp": "2025-11-19T20:51:46.676Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index a46e281974b..45f0cfcd727 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": "2025-11-10T11:41:47.090Z", + "timestamp": "2025-11-19T20:51:46.917Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index 9715b82c6ed..72d52ec296c 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": "2025-11-10T11:41:47.321Z", + "timestamp": "2025-11-19T20:51:47.152Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index fa04ecadb59..8775c8fd162 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": "2025-11-10T11:41:47.347Z", + "timestamp": "2025-11-19T20:51:47.212Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index 4ea42a6ab1e..9987d6d468e 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": "2025-11-10T11:41:47.624Z", + "timestamp": "2025-11-19T20:51:47.488Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index 910fa68f0ad..4b74ae80cf4 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": "2025-11-10T11:41:48.258Z", + "timestamp": "2025-11-19T20:51:48.083Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index 78bcc92cc5d..0c10acf9c32 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": "2025-11-10T11:41:48.259Z", + "timestamp": "2025-11-19T20:51:48.083Z", "disclosures": [ { "identifier": "sa-camp-*", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index 553db156bb7..345eef10b8e 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": "2025-11-10T11:41:48.290Z", + "timestamp": "2025-11-19T20:51:48.112Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index 2cd45e7dbff..8979658efb2 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": "2025-11-10T11:41:48.302Z", + "timestamp": "2025-11-19T20:51:48.129Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 0d3bb5ca220..431da5d0b5e 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": "2025-11-10T11:41:48.710Z", + "timestamp": "2025-11-19T20:51:48.464Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index c60d81765d4..ed5e5e42678 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": "2025-11-10T11:41:49.253Z", + "timestamp": "2025-11-19T20:51:49.146Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index a2cb1f2a552..fb580391c59 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": "2025-11-10T11:41:49.275Z", + "timestamp": "2025-11-19T20:51:49.410Z", "disclosures": [ { "identifier": "trc_cookie_storage", @@ -240,17 +240,28 @@ { "identifier": "taboola:shopify:test", "type": "web", - "maxAgeSeconds": null + "maxAgeSeconds": null, + "purposes": [ + 1 + ] }, { "identifier": "taboola:shopify:enable_debug_logging", "type": "web", - "maxAgeSeconds": null + "maxAgeSeconds": null, + "purposes": [ + 1, + 10 + ] }, { "identifier": "taboola:shopify:pixel_allow_checkout_start", "type": "web", - "maxAgeSeconds": null + "maxAgeSeconds": null, + "purposes": [ + 1, + 3 + ] }, { "identifier": "taboola:shopify:page_view", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index 6a276c7f121..0a528a2a855 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": "2025-11-10T11:41:49.922Z", + "timestamp": "2025-11-19T20:51:50.104Z", "disclosures": [ { "identifier": "trc_cookie_storage", @@ -240,17 +240,28 @@ { "identifier": "taboola:shopify:test", "type": "web", - "maxAgeSeconds": null + "maxAgeSeconds": null, + "purposes": [ + 1 + ] }, { "identifier": "taboola:shopify:enable_debug_logging", "type": "web", - "maxAgeSeconds": null + "maxAgeSeconds": null, + "purposes": [ + 1, + 10 + ] }, { "identifier": "taboola:shopify:pixel_allow_checkout_start", "type": "web", - "maxAgeSeconds": null + "maxAgeSeconds": null, + "purposes": [ + 1, + 3 + ] }, { "identifier": "taboola:shopify:page_view", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index ee6e9263f76..c1eff5141fc 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": "2025-11-10T11:41:49.922Z", + "timestamp": "2025-11-19T20:51:50.104Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index 05d240778e9..0820c6e91d0 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": "2025-11-10T11:41:49.923Z", + "timestamp": "2025-11-19T20:51:50.105Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index 8e27bc2dba4..d928341fda6 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": "2025-11-10T11:41:49.956Z", + "timestamp": "2025-11-19T20:51:50.138Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index 95500baecbb..0d1c9da97ec 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": "2025-11-10T11:41:49.956Z", + "timestamp": "2025-11-19T20:51:50.138Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index 74e7becdbc9..b05bfee171a 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": "2025-11-10T11:41:49.972Z", + "timestamp": "2025-11-19T20:51:50.164Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index e00d6c1ff8a..f3000f6cc1d 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": "2025-11-10T11:41:49.973Z", + "timestamp": "2025-11-19T20:51:50.165Z", "disclosures": [] } }, diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index b0c41f66ad0..4159d72a123 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": "2025-11-10T11:41:50.001Z", + "timestamp": "2025-11-19T20:51:50.204Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index 4f3af0090f7..3a1e57ff65b 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": "2025-11-10T11:41:10.869Z", + "timestamp": "2025-11-19T20:51:08.093Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json new file mode 100644 index 00000000000..32944cafd7b --- /dev/null +++ b/metadata/modules/toponBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://mores.toponad.net/tmp/tpn/toponads_tcf_disclosure.json": { + "timestamp": "2025-11-19T20:51:50.224Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "topon", + "aliasOf": null, + "gvlid": 1305, + "disclosureURL": "https://mores.toponad.net/tmp/tpn/toponads_tcf_disclosure.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index 10eccf8ed29..e6ad4e4afce 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": "2025-11-10T11:41:50.019Z", + "timestamp": "2025-11-19T20:51:50.269Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index 23f05094160..ef523655772 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": "2025-11-10T11:41:50.055Z", + "timestamp": "2025-11-19T20:51:50.328Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index baf0cf9234d..41d3ecd154e 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": "2025-11-10T11:41:50.055Z", + "timestamp": "2025-11-19T20:51:50.328Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index cd930f8c082..f5b3989c181 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": "2025-11-10T11:41:50.096Z", + "timestamp": "2025-11-19T20:51:50.429Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index 74ab388c170..4f7aa901969 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": "2025-11-10T11:41:50.138Z", + "timestamp": "2025-11-19T20:51:50.453Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index ddd4738a1b3..56adb8f75ad 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": "2025-11-10T11:41:50.150Z", + "timestamp": "2025-11-19T20:51:50.469Z", "disclosures": [] } }, diff --git a/metadata/modules/uniquestWidgetBidAdapter.json b/metadata/modules/uniquest_widgetBidAdapter.json similarity index 100% rename from metadata/modules/uniquestWidgetBidAdapter.json rename to metadata/modules/uniquest_widgetBidAdapter.json diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index 8c59ce6b2ed..bb5a9485c71 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": "2025-11-10T11:41:50.151Z", + "timestamp": "2025-11-19T20:51:50.470Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index e458514d045..1bade253a58 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": "2025-11-10T11:41:10.871Z", + "timestamp": "2025-11-19T20:51:08.095Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index a61cd049897..bbfc0ae04cb 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": "2025-11-10T11:41:50.151Z", + "timestamp": "2025-11-19T20:51:50.470Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index 94368486ebd..1b88f4f1597 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": "2025-11-10T11:41:50.152Z", + "timestamp": "2025-11-19T20:51:50.471Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index 557fba73ab0..60290f5246e 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": "2025-11-10T11:41:10.870Z", + "timestamp": "2025-11-19T20:51:08.094Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index 707ff346c82..dd16d6190e0 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": "2025-11-10T11:41:50.153Z", + "timestamp": "2025-11-19T20:51:50.471Z", "disclosures": [] } }, diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index 3668e9c6ed0..18fb84cd5f1 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": "2025-11-10T11:41:50.374Z", + "timestamp": "2025-11-19T20:51:50.772Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index b1c3730fa2c..0d89d6b88ac 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": "2025-11-10T11:41:50.428Z", + "timestamp": "2025-11-19T20:51:50.839Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index 639b38d5fb4..52ef397af3b 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": "2025-11-10T11:41:50.685Z", + "timestamp": "2025-11-19T20:51:51.075Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 7e245658def..079ec87a9a4 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": "2025-11-10T11:41:50.686Z", + "timestamp": "2025-11-19T20:51:51.077Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index 48ea4a5f5e3..66ed24bcdfa 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": "2025-11-10T11:41:50.873Z", + "timestamp": "2025-11-19T20:51:51.275Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index b730e79f892..5f5ff6e0aca 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": "2025-11-10T11:41:51.151Z", + "timestamp": "2025-11-19T20:51:51.299Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index e399ed2ecfa..53853ab1808 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": "2025-11-10T11:41:51.151Z", + "timestamp": "2025-11-19T20:51:51.299Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index 94f8854e5b5..ffe75e2969c 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": "2025-11-10T11:41:51.344Z", + "timestamp": "2025-11-19T20:51:51.516Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index f35724ba33e..00865923c62 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": "2025-11-10T11:41:51.609Z", + "timestamp": "2025-11-19T20:51:51.832Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index e81651fc5d9..60162f3b72a 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": "2025-11-10T11:41:51.862Z", + "timestamp": "2025-11-19T20:51:52.087Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index 72477d44571..f4c7bafdc37 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": "2025-11-10T11:41:52.261Z", + "timestamp": "2025-11-19T20:51:52.466Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index 8b463642ed0..3dfd82d0c46 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": "2025-11-10T11:41:52.262Z", + "timestamp": "2025-11-19T20:51:52.467Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index 0bc1db14bcf..56b0890884e 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": "2025-11-10T11:41:52.368Z", + "timestamp": "2025-11-19T20:51:52.573Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index b3c00bee6ba..c52a5ce1d5d 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": "2025-11-10T11:41:52.383Z", + "timestamp": "2025-11-19T20:51:52.596Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index 855820dd6ca..efb60121dd1 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": "2025-11-10T11:41:52.487Z", + "timestamp": "2025-11-19T20:51:52.678Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index b0ed82623ce..a6ae367f9b8 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": "2025-11-10T11:41:52.598Z", + "timestamp": "2025-11-19T20:51:52.801Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index 4b1ff7114f9..0f39ead30bf 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": "2025-11-10T11:41:52.688Z", + "timestamp": "2025-11-19T20:51:52.932Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index a32ff166351..5c03469f26e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.17.0-pre", + "version": "10.17.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.17.0-pre", + "version": "10.17.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", @@ -77,7 +77,6 @@ "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.0.1", "karma-coverage-istanbul-reporter": "^3.0.3", - "karma-edge-launcher": "^0.4.2", "karma-firefox-launcher": "^2.1.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", @@ -7271,9 +7270,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001755", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz", - "integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==", + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", "funding": [ { "type": "opencollective", @@ -8901,12 +8900,6 @@ "wcwidth": "^1.0.1" } }, - "node_modules/edge-launcher": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/edge-launcher/-/edge-launcher-1.2.2.tgz", - "integrity": "sha512-JcD5WBi3BHZXXVSSeEhl6sYO8g5cuynk/hifBzds2Bp4JdzCGLNMHgMCKu5DvrO1yatMgF0goFsxXRGus0yh1g==", - "dev": true - }, "node_modules/edge-paths": { "version": "3.0.5", "dev": true, @@ -14728,21 +14721,6 @@ "semver": "bin/semver" } }, - "node_modules/karma-edge-launcher": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz", - "integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==", - "dev": true, - "dependencies": { - "edge-launcher": "1.2.2" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "karma": ">=0.9" - } - }, "node_modules/karma-firefox-launcher": { "version": "2.1.3", "dev": true, diff --git a/package.json b/package.json index e90363ae71b..8314bfb6648 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.17.0-pre", + "version": "10.17.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 89843cb9a2b3cc7d547603791cbeb7ba42583b28 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 19 Nov 2025 20:53:01 +0000 Subject: [PATCH 129/147] Increment version to 10.18.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 5c03469f26e..ad266720dee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.17.0", + "version": "10.18.0-pre", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.17.0", + "version": "10.18.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 8314bfb6648..439d82f77af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.17.0", + "version": "10.18.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 28ccb4eb59398a0f043c6fc767ee7552e1fc98c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:36:11 -0500 Subject: [PATCH 130/147] Bump glob from 10.4.5 to 10.5.0 (#14170) Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0. - [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md) - [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0) --- updated-dependencies: - dependency-name: glob dependency-version: 10.5.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Patrick McCann --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index ad266720dee..b9fe9c6c2d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11596,7 +11596,9 @@ "license": "ISC" }, "node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { From 0e8539e11065b9db5fa3ab9f07f6f18e4e9e8917 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:37:02 -0500 Subject: [PATCH 131/147] Bump axios from 1.9.0 to 1.13.2 (#14161) Bumps [axios](https://github.com/axios/axios) from 1.9.0 to 1.13.2. - [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.9.0...v1.13.2) --- updated-dependencies: - dependency-name: axios dependency-version: 1.13.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Patrick McCann --- package-lock.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9fe9c6c2d5..2da5c893b64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6654,12 +6654,14 @@ } }, "node_modules/axios": { - "version": "1.9.0", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, From 668f2fec217d0482e0266aee37d446ffa208a252 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Nov 2025 10:46:35 -0500 Subject: [PATCH 132/147] CI: do not use browserstack for e2e tests (#14174) * local e2e testing * rework concurrency id * clean up inputs * try installing safari * specify shell * try npx * break out build from test for e2e * try safaridriver --enable * adjust build, safaridriver * safari why such a pain * safari * safari... * do not allow parallel tests * Cache deps --- .github/actions/npm-ci/action.yml | 23 +++++++++++ .github/workflows/browser-tests.yml | 54 +++++++++++++------------- .github/workflows/browser_testing.json | 4 ++ .github/workflows/run-tests.yml | 38 +++++++++++++++++- .github/workflows/test.yml | 20 +--------- gulpfile.js | 1 + wdio.local.conf.js | 19 ++++++++- 7 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 .github/actions/npm-ci/action.yml diff --git a/.github/actions/npm-ci/action.yml b/.github/actions/npm-ci/action.yml new file mode 100644 index 00000000000..c23b3f455d6 --- /dev/null +++ b/.github/actions/npm-ci/action.yml @@ -0,0 +1,23 @@ +name: NPM install +description: Run npm install and cache dependencies + +runs: + using: 'composite' + steps: + - name: Restore dependencies + id: restore-modules + uses: actions/cache/restore@v4 + with: + path: "node_modules" + key: node_modules-${{ hashFiles('package-lock.json') }} + - name: Run npm ci + if: ${{ steps.restore-modules.outputs.cache-hit != 'true' }} + shell: bash + run: | + npm ci + - name: Cache dependencies + if: ${{ steps.restore-modules.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v4 + with: + path: "node_modules" + key: node_modules-${{ hashFiles('package-lock.json') }} diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml index 662bf38d9f1..8dfc9dcf665 100644 --- a/.github/workflows/browser-tests.yml +++ b/.github/workflows/browser-tests.yml @@ -1,29 +1,10 @@ name: Run unit tests on all browsers on: workflow_call: - inputs: - chunks: - description: Number of chunks to split tests into - required: false - type: number - default: 1 - build-cmd: - description: Build command, run once - required: false - type: string - test-cmd: - description: Test command, run once per chunk - required: true - type: string - timeout: - description: Timeout on test run - required: false - type: number - default: 10 outputs: coverage: description: Artifact name for coverage results - value: ${{ jobs.browser-tests.outputs.coverage }} + value: ${{ jobs.unit-tests.outputs.coverage }} secrets: BROWSERSTACK_USER_NAME: description: "Browserstack user name" @@ -33,7 +14,7 @@ jobs: build: uses: ./.github/workflows/build.yml with: - build-cmd: ${{ inputs.build-cmd }} + build-cmd: npx gulp build setup: needs: build @@ -85,9 +66,9 @@ jobs: built-key: ${{ needs.build.outputs.built-key }} test-cmd: gulp test-build-logic - browser-tests: + e2e-tests: needs: [setup, build] - name: "Browser: ${{ matrix.browser.name }}" + name: "E2E (browser: ${{ matrix.browser.wdioName }})" strategy: fail-fast: false matrix: @@ -95,9 +76,28 @@ jobs: uses: ./.github/workflows/run-tests.yml with: + browser: ${{ matrix.browser.wdioName }} built-key: ${{ needs.build.outputs.built-key }} - test-cmd: ${{ inputs.test-cmd }} --browsers ${{ matrix.browser.name }} ${{ matrix.browser.coverage && '--coverage' || '--no-coverage' }} - chunks: ${{ inputs.chunks }} + test-cmd: npx gulp e2e-test-nobuild --local + chunks: 1 + runs-on: ${{ matrix.browser.runsOn || 'ubuntu-latest' }} + install-safari: ${{ matrix.browser.runsOn == 'macos-latest' }} + run-npm-install: ${{ matrix.browser.runsOn == 'windows-latest' }} + browserstack: false + + unit-tests: + needs: [setup, build] + name: "Unit (browser: ${{ matrix.browser.name }})" + strategy: + fail-fast: false + matrix: + browser: ${{ fromJSON(needs.setup.outputs.browsers) }} + uses: + ./.github/workflows/run-tests.yml + with: + built-key: ${{ needs.build.outputs.built-key }} + test-cmd: npx gulp test-only-nobuild --browsers ${{ matrix.browser.name }} ${{ matrix.browser.coverage && '--coverage' || '--no-coverage' }} + chunks: 8 runs-on: ${{ matrix.browser.runsOn || 'ubuntu-latest' }} browserstack-tests: @@ -108,8 +108,8 @@ jobs: ./.github/workflows/run-tests.yml with: built-key: ${{ needs.setup.outputs.bstack-key }} - test-cmd: ${{ inputs.test-cmd }} --browserstack --no-coverage - chunks: ${{ inputs.chunks }} + test-cmd: npx gulp test-only-nobuild --browserstack --no-coverage + chunks: 8 browserstack: true secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} diff --git a/.github/workflows/browser_testing.json b/.github/workflows/browser_testing.json index aee1631ead3..debfef278e1 100644 --- a/.github/workflows/browser_testing.json +++ b/.github/workflows/browser_testing.json @@ -1,17 +1,21 @@ { "ChromeHeadless": { "bsName": "chrome", + "wdioName": "chrome", "coverage": true }, "EdgeHeadless": { "bsName": "edge", + "wdioName": "msedge", "runsOn": "windows-latest" }, "SafariNative": { + "wdioName": "safari technology preview", "bsName": "safari", "runsOn": "macos-latest" }, "FirefoxHeadless": { + "wdioName": "firefox", "bsName": "firefox" } } diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c1bb56f931c..36b1bca4b4d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,6 +2,10 @@ name: Run unit tests on: workflow_call: inputs: + browser: + description: values to set as the BROWSER env variable + required: false + type: string chunks: description: Number of chunks to split tests into required: false @@ -34,6 +38,16 @@ on: required: false default: ubuntu-latest type: string + install-safari: + description: Install Safari + type: boolean + required: false + default: false + run-npm-install: + description: Run npm install before tests + type: boolean + required: false + default: false outputs: coverage: description: Artifact name for coverage results @@ -50,11 +64,14 @@ jobs: runs-on: ubuntu-latest outputs: chunks: ${{ steps.chunks.outputs.chunks }} + id: ${{ steps.chunks.outputs.id }} steps: - name: Define chunks id: chunks run: | - echo 'chunks=[ '$(seq --separator=, 1 1 ${{ inputs.chunks }})' ]' >> "$GITHUB_OUTPUT" + echo 'chunks=[ '$(seq --separator=, 1 1 ${{ inputs.chunks }})' ]' >> out; + echo 'id='"$(uuidgen)" >> out; + cat out >> "$GITHUB_OUTPUT"; build: uses: ./.github/workflows/build.yml @@ -76,6 +93,7 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} TEST_CHUNKS: ${{ inputs.chunks }} TEST_CHUNK: ${{ matrix.chunk-no }} + BROWSER: ${{ inputs.browser }} outputs: coverage: ${{ steps.coverage.outputs.coverage }} concurrency: @@ -83,7 +101,7 @@ jobs: # Ideally we'd like to serialize browserstack access across all workflows, but github's max queue length is only 1 # (cfr. https://github.com/orgs/community/discussions/12835) # so we add the run_id to serialize only within one push / pull request (which has the effect of queueing e2e and unit tests) - group: ${{ inputs.browserstack && 'browser' || github.run_id }}${{ inputs.browserstack && 'stac' || inputs.test-cmd }}${{ inputs.browserstack && 'k' || matrix.chunk-no }}-${{ github.run_id }} + group: ${{ inputs.browserstack && format('browserstack-{0}', github.run_id) || format('{0}-{1}', needs.checkout.outputs.id, matrix.chunk-no) }} cancel-in-progress: false runs-on: ${{ inputs.runs-on }} @@ -96,6 +114,21 @@ jobs: with: name: ${{ needs.build.outputs.built-key }} + - name: Install safari + if: ${{ inputs.install-safari }} + run: | + brew install --cask safari-technology-preview + defaults write com.apple.Safari IncludeDevelopMenu YES + defaults write com.apple.Safari AllowRemoteAutomation 1 + sudo safaridriver --enable + sudo "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" --enable + + + - name: Run npm install + if: ${{ inputs.run-npm-install }} + run: | + npm install + - name: 'BrowserStack Env Setup' if: ${{ inputs.browserstack }} uses: 'browserstack/github-actions/setup-env@master' @@ -121,6 +154,7 @@ jobs: timeout_minutes: ${{ inputs.timeout }} max_attempts: 3 command: ${{ inputs.test-cmd }} + shell: bash - name: 'BrowserStackLocal Stop' if: ${{ inputs.browserstack }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 658af3a5731..c56b8cae4c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: echo base-commit="${{ github.event.pull_request.base.sha || github.event.before }}" >> $GITHUB_OUTPUT - name: Install dependencies - run: npm ci + uses: ./.github/actions/npm-ci - name: 'Save working directory' uses: ./.github/actions/save @@ -88,25 +88,9 @@ jobs: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} test: - name: "Unit tests (all features enabled + coverage)" + name: "Browser tests" needs: checkout uses: ./.github/workflows/browser-tests.yml - with: - chunks: 8 - build-cmd: npx gulp precompile - test-cmd: npx gulp test-only-nobuild - secrets: - BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - - test-e2e: - name: End-to-end tests - needs: checkout - uses: ./.github/workflows/run-tests.yml - with: - test-cmd: npx gulp e2e-test - browserstack: true - timeout: 15 secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} diff --git a/gulpfile.js b/gulpfile.js index cd51c89f5e1..e5a4db41884 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -554,6 +554,7 @@ gulp.task('default', gulp.series('build')); gulp.task('e2e-test-only', gulp.series(requireNodeVersion(16), () => runWebdriver({file: argv.file}))); gulp.task('e2e-test', gulp.series(requireNodeVersion(16), clean, 'build-bundle-prod', e2eTestTaskMaker())); +gulp.task('e2e-test-nobuild', gulp.series(requireNodeVersion(16), e2eTestTaskMaker())) // other tasks gulp.task(bundleToStdout); diff --git a/wdio.local.conf.js b/wdio.local.conf.js index 772448472bf..74c7ac3a3ee 100644 --- a/wdio.local.conf.js +++ b/wdio.local.conf.js @@ -1,4 +1,5 @@ const shared = require('./wdio.shared.conf.js'); +const process = require('process'); exports.config = { ...shared.config, @@ -9,5 +10,21 @@ exports.config = { args: ['headless', 'disable-gpu'], }, }, - ], + { + browserName: 'firefox', + 'moz:firefoxOptions': { + args: ['-headless'] + } + }, + { + browserName: 'msedge', + 'ms:edgeOptions': { + args: ['--headless'] + } + }, + { + browserName: 'safari technology preview' + } + ].filter((cap) => cap.browserName === (process.env.BROWSER ?? 'chrome')), + maxInstancesPerCapability: 1 }; From e5995dd6d7e50c406f0884426b9b8c3ae02be79b Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 Nov 2025 10:47:11 -0500 Subject: [PATCH 133/147] Core: Delete .circleci/config.yml (#14137) No longer needed in 10 --- .circleci/config.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 8eb8b28c570..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2.1 -jobs: - noop: - docker: - - image: cimg/base:stable - steps: - - run: echo "CircleCI build skipped - using GitHub Actions. This job can be removed once 9.x is no longer supported." -workflows: - version: 2 - default: - jobs: - - noop From b92867b2ea6aa1becbc2a82dd35bda7e303cc780 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Nov 2025 13:15:20 -0500 Subject: [PATCH 134/147] CI: Automatic review assignment (#14176) * test arguments * declare environ * environment name * I like secrets * Revert "I like secrets" This reverts commit dc07a6acbb4743d9011cea225a5bbed8ca334d13. * try getPRProperties * local e2e testing * rework concurrency id * clean up inputs * try installing safari * specify shell * try npx * break out build from test for e2e * try safaridriver --enable * adjust build, safaridriver * safari why such a pain * safari * safari... * do not allow parallel tests * test integration * self-contained workflow * Cache deps * Install dependencies * pass PR number * WIP pr bot * Update PR assignment script * typo * undo unrelated change --- .github/workflows/PR-assignment.yml | 59 ++++++++ .github/workflows/scripts/assignReviewers.js | 38 +++++ .github/workflows/scripts/getPRProperties.js | 149 +++++++++++++++++++ .github/workflows/scripts/ghRequest.js | 9 ++ 4 files changed, 255 insertions(+) create mode 100644 .github/workflows/PR-assignment.yml create mode 100644 .github/workflows/scripts/assignReviewers.js create mode 100644 .github/workflows/scripts/getPRProperties.js create mode 100644 .github/workflows/scripts/ghRequest.js diff --git a/.github/workflows/PR-assignment.yml b/.github/workflows/PR-assignment.yml new file mode 100644 index 00000000000..1ae8a1ae249 --- /dev/null +++ b/.github/workflows/PR-assignment.yml @@ -0,0 +1,59 @@ +name: Assign PR reviewers +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + assign_reviewers: + name: Assign reviewers + runs-on: ubuntu-latest + + steps: + - name: Generate app token + id: token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PR_BOT_ID }} + private-key: ${{ secrets.PR_BOT_PEM }} + + - name: Checkout + uses: actions/checkout@v5 + with: + ref: refs/pull/${{ github.event.pull_request.number }}/head + + - name: Install dependencies + uses: ./.github/actions/npm-ci + - name: Build + run: | + npx gulp build + - name: Install s3 client + run: | + npm install @aws-sdk/client-s3 + - name: Get PR properties + id: get-props + uses: actions/github-script@v8 + env: + AWS_ACCESS_KEY_ID: ${{ vars.PR_BOT_AWS_AK }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.PR_BOT_AWS_SAK }} + with: + github-token: ${{ steps.token.outputs.token }} + script: | + const getProps = require('./.github/workflows/scripts/getPRProperties.js') + const props = await getProps({ + github, + context, + prNo: ${{ github.event.pull_request.number }}, + reviewerTeam: '${{ vars.REVIEWER_TEAM }}', + engTeam: '${{ vars.ENG_TEAM }}' + }); + console.log('PR properties:', JSON.stringify(props, null, 2)); + return props; + - name: Assign reviewers + if: ${{ !fromJSON(steps.get-props.outputs.result).review.ok }} + uses: actions/github-script@v8 + with: + github-token: ${{ steps.token.outputs.token }} + script: | + const assignReviewers = require('./.github/workflows/scripts/assignReviewers.js') + const reviewers = await assignReviewers({github, context, prData: ${{ steps.get-props.outputs.result }} }); + console.log('Assigned reviewers:', JSON.stringify(reviewers, null, 2)); diff --git a/.github/workflows/scripts/assignReviewers.js b/.github/workflows/scripts/assignReviewers.js new file mode 100644 index 00000000000..11e62e7e99c --- /dev/null +++ b/.github/workflows/scripts/assignReviewers.js @@ -0,0 +1,38 @@ +const ghRequester = require('./ghRequest.js'); + +function pickFrom(candidates, exclude, no) { + exclude = exclude.slice(); + const winners = []; + while (winners.length < no) { + const candidate = candidates[Math.floor(Math.random() * candidates.length)]; + if (!exclude.includes(candidate)) { + winners.push(candidate); + exclude.push(candidate); + } + } + return winners; +} + +async function assignReviewers({github, context, prData}) { + const reviewers = prData.review.reviewers.map(rv => rv.login); + const missingPrebidEng = prData.review.requires.prebidEngineers - prData.review.prebidEngineers; + const missingPrebidReviewers = prData.review.requires.prebidReviewers - prData.review.prebidReviewers - missingPrebidEng; + + if (missingPrebidEng > 0) { + reviewers.push(...pickFrom(prData.prebidEngineers, [...reviewers, prData.author.login], missingPrebidEng)) + } + if (missingPrebidReviewers > 0) { + reviewers.push(...pickFrom(prData.prebidReviewers, [...reviewers, prData.author.login], missingPrebidReviewers)) + } + + const request = ghRequester(github); + await request('POST /repos/{owner}/{repo}/pulls/{prNo}/requested_reviewers', { + owner: context.repo.owner, + repo: context.repo.repo, + prNo: prData.pr, + reviewers + }) + return reviewers; +} + +module.exports = assignReviewers; diff --git a/.github/workflows/scripts/getPRProperties.js b/.github/workflows/scripts/getPRProperties.js new file mode 100644 index 00000000000..c4eca6183a1 --- /dev/null +++ b/.github/workflows/scripts/getPRProperties.js @@ -0,0 +1,149 @@ +const ghRequester = require('./ghRequest.js'); +const AWS = require("@aws-sdk/client-s3"); + +const MODULE_PATTERNS = [ + /^modules\/([^\/]+)BidAdapter(\.(\w+)|\/)/, + /^modules\/([^\/]+)AnalyticsAdapter(\.(\w+)|\/)/, + /^modules\/([^\/]+)RtdProvider(\.(\w+)|\/)/, + /^modules\/([^\/]+)IdSystem(\.(\w+)|\/)/ +] + +const EXCLUDE_PATTERNS = [ + /^test\//, + /^integrationExamples\// +] +const LIBRARY_PATTERN = /^libraries\/([^\/]+)\//; + +function extractVendor(chunkName) { + for (const pat of MODULE_PATTERNS) { + const match = pat.exec(`modules/${chunkName}`); + if (match != null) { + return match[1]; + } + } + return chunkName; +} + +const getLibraryRefs = (() => { + const deps = require('../../../build/dist/dependencies.json'); + const refs = {}; + return function (libraryName) { + if (!refs.hasOwnProperty(libraryName)) { + refs[libraryName] = new Set(); + Object.entries(deps) + .filter(([name, deps]) => deps.includes(`${libraryName}.js`)) + .forEach(([name]) => refs[libraryName].add(extractVendor(name))) + } + return refs[libraryName]; + } +})(); + +function isCoreFile(path) { + if (EXCLUDE_PATTERNS.find(pat => pat.test(path))) { + return false; + } + if (MODULE_PATTERNS.find(pat => pat.test(path)) ) { + return false; + } + const lib = LIBRARY_PATTERN.exec(path); + if (lib != null) { + // a library is "core" if it's used by more than one vendor + return getLibraryRefs(lib[1]).size > 1; + } + return true; +} + +async function isPrebidMember(ghHandle) { + const client = new AWS.S3({region: 'us-east-2'}); + const res = await client.getObject({ + Bucket: 'repo-dashboard-files-891377123989', + Key: 'memberMapping.json' + }); + const members = JSON.parse(await res.Body.transformToString()); + return members.includes(ghHandle); +} + + +async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { + const request = ghRequester(github); + let [files, pr, prebidReviewers, prebidEngineers] = await Promise.all([ + request('GET /repos/{owner}/{repo}/pulls/{prNo}/files', { + owner: context.repo.owner, + repo: context.repo.repo, + prNo, + }), + request('GET /repos/{owner}/{repo}/pulls/{prNo}', { + owner: context.repo.owner, + repo: context.repo.repo, + prNo, + }), + ...[reviewerTeam, engTeam].map(team => request('GET /orgs/{org}/teams/{team}/members', { + org: context.repo.owner, + team, + })) + ]); + prebidReviewers = prebidReviewers.data.map(datum => datum.login); + prebidEngineers = prebidEngineers.data.map(datum=> datum.login); + let isCoreChange = false; + files = files.data.map(datum => datum.filename).map(file => { + const core = isCoreFile(file); + if (core) isCoreChange = true; + return { + file, + core + } + }); + const review = { + prebidEngineers: 0, + prebidReviewers: 0, + reviewers: [] + }; + const author = pr.data.user.login; + pr.data.requested_reviewers + .map(rv => rv.login) + .forEach(reviewer => { + if (reviewer === author) return; + const isPrebidEngineer = prebidEngineers.includes(reviewer); + const isPrebidReviewer = isPrebidEngineer || prebidReviewers.includes(reviewer); + if (isPrebidEngineer) { + review.prebidEngineers += 1; + } + if (isPrebidReviewer) { + review.prebidReviewers += 1 + } + review.reviewers.push({ + login: reviewer, + isPrebidEngineer, + isPrebidReviewer, + }) + }); + const data = { + pr: prNo, + author: { + login: author, + isPrebidMember: await isPrebidMember(author) + }, + isCoreChange, + files, + prebidReviewers, + prebidEngineers, + review, + } + data.review.requires = reviewRequirements(data); + data.review.ok = satisfiesReviewRequirements(data.review); + return data; +} + +function reviewRequirements(prData) { + return { + prebidEngineers: prData.author.isPrebidMember ? 1 : 0, + prebidReviewers: prData.isCoreChange ? 2 : 1 + } +} + +function satisfiesReviewRequirements({requires, prebidEngineers, prebidReviewers}) { + return prebidEngineers >= requires.prebidEngineers && prebidReviewers >= requires.prebidReviewers +} + + +module.exports = getPRProperties; diff --git a/.github/workflows/scripts/ghRequest.js b/.github/workflows/scripts/ghRequest.js new file mode 100644 index 00000000000..cc09edaf390 --- /dev/null +++ b/.github/workflows/scripts/ghRequest.js @@ -0,0 +1,9 @@ +module.exports = function githubRequester(github) { + return function (verb, params) { + return github.request(verb, Object.assign({ + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }, params)) + } +} From 8c5b2a067cef963ac44104c8108504c7f4c33e91 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Nov 2025 14:00:42 -0500 Subject: [PATCH 135/147] CI: improve PR review assignment automation (#14177) * test arguments * declare environ * environment name * I like secrets * Revert "I like secrets" This reverts commit dc07a6acbb4743d9011cea225a5bbed8ca334d13. * try getPRProperties * local e2e testing * rework concurrency id * clean up inputs * try installing safari * specify shell * try npx * break out build from test for e2e * try safaridriver --enable * adjust build, safaridriver * safari why such a pain * safari * safari... * do not allow parallel tests * test integration * self-contained workflow * Cache deps * Install dependencies * pass PR number * WIP pr bot * Update PR assignment script * typo * undo unrelated change * look at reviews as well as requested reviewers * add an extra authorized reviewer check * Do not consider negative engineers --- .github/workflows/PR-assignment.yml | 3 ++- .github/workflows/scripts/assignReviewers.js | 2 +- .github/workflows/scripts/getPRProperties.js | 20 +++++++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/PR-assignment.yml b/.github/workflows/PR-assignment.yml index 1ae8a1ae249..5bac9b5d763 100644 --- a/.github/workflows/PR-assignment.yml +++ b/.github/workflows/PR-assignment.yml @@ -44,7 +44,8 @@ jobs: context, prNo: ${{ github.event.pull_request.number }}, reviewerTeam: '${{ vars.REVIEWER_TEAM }}', - engTeam: '${{ vars.ENG_TEAM }}' + engTeam: '${{ vars.ENG_TEAM }}', + authReviewTeam: '${{ vars.AUTH_REVIEWER_TEAM }}' }); console.log('PR properties:', JSON.stringify(props, null, 2)); return props; diff --git a/.github/workflows/scripts/assignReviewers.js b/.github/workflows/scripts/assignReviewers.js index 11e62e7e99c..6d18a3da0f6 100644 --- a/.github/workflows/scripts/assignReviewers.js +++ b/.github/workflows/scripts/assignReviewers.js @@ -16,7 +16,7 @@ function pickFrom(candidates, exclude, no) { async function assignReviewers({github, context, prData}) { const reviewers = prData.review.reviewers.map(rv => rv.login); const missingPrebidEng = prData.review.requires.prebidEngineers - prData.review.prebidEngineers; - const missingPrebidReviewers = prData.review.requires.prebidReviewers - prData.review.prebidReviewers - missingPrebidEng; + const missingPrebidReviewers = prData.review.requires.prebidReviewers - prData.review.prebidReviewers - (missingPrebidEng > 0 ? missingPrebidEng : 0); if (missingPrebidEng > 0) { reviewers.push(...pickFrom(prData.prebidEngineers, [...reviewers, prData.author.login], missingPrebidEng)) diff --git a/.github/workflows/scripts/getPRProperties.js b/.github/workflows/scripts/getPRProperties.js index c4eca6183a1..902709db08a 100644 --- a/.github/workflows/scripts/getPRProperties.js +++ b/.github/workflows/scripts/getPRProperties.js @@ -64,9 +64,9 @@ async function isPrebidMember(ghHandle) { } -async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { +async function getPRProperties({github, context, prNo, reviewerTeam, engTeam, authReviewTeam}) { const request = ghRequester(github); - let [files, pr, prebidReviewers, prebidEngineers] = await Promise.all([ + let [files, pr, prReviews, prebidReviewers, prebidEngineers, authorizedReviewers] = await Promise.all([ request('GET /repos/{owner}/{repo}/pulls/{prNo}/files', { owner: context.repo.owner, repo: context.repo.repo, @@ -77,13 +77,19 @@ async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { repo: context.repo.repo, prNo, }), - ...[reviewerTeam, engTeam].map(team => request('GET /orgs/{org}/teams/{team}/members', { + request('GET /repos/{owner}/{repo}/pulls/{prNo}/reviews', { + owner: context.repo.owner, + repo: context.repo.repo, + prNo, + }), + ...[reviewerTeam, engTeam, authReviewTeam].map(team => request('GET /orgs/{org}/teams/{team}/members', { org: context.repo.owner, team, })) ]); prebidReviewers = prebidReviewers.data.map(datum => datum.login); prebidEngineers = prebidEngineers.data.map(datum=> datum.login); + authorizedReviewers = authorizedReviewers.data.map(datum=> datum.login); let isCoreChange = false; files = files.data.map(datum => datum.filename).map(file => { const core = isCoreFile(file); @@ -99,12 +105,16 @@ async function getPRProperties({github, context, prNo, reviewerTeam, engTeam}) { reviewers: [] }; const author = pr.data.user.login; + const allReviewers = new Set(); pr.data.requested_reviewers - .map(rv => rv.login) + .forEach(rv => allReviewers.add(rv.login)); + prReviews.data.forEach(datum => allReviewers.add(datum.user.login)); + + allReviewers .forEach(reviewer => { if (reviewer === author) return; const isPrebidEngineer = prebidEngineers.includes(reviewer); - const isPrebidReviewer = isPrebidEngineer || prebidReviewers.includes(reviewer); + const isPrebidReviewer = isPrebidEngineer || prebidReviewers.includes(reviewer) || authorizedReviewers.includes(reviewer); if (isPrebidEngineer) { review.prebidEngineers += 1; } From bfa9fb861dce6652f7ed6cc98258d05898fd3b9e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Nov 2025 14:21:23 -0500 Subject: [PATCH 136/147] CI: fix issue with PR review automation (#14179) * test arguments * declare environ * environment name * I like secrets * Revert "I like secrets" This reverts commit dc07a6acbb4743d9011cea225a5bbed8ca334d13. * try getPRProperties * local e2e testing * rework concurrency id * clean up inputs * try installing safari * specify shell * try npx * break out build from test for e2e * try safaridriver --enable * adjust build, safaridriver * safari why such a pain * safari * safari... * do not allow parallel tests * test integration * self-contained workflow * Cache deps * Install dependencies * pass PR number * WIP pr bot * Update PR assignment script * typo * undo unrelated change * look at reviews as well as requested reviewers * add an extra authorized reviewer check * Do not consider negative engineers * do not request reviewers that have already reviewed --- .github/workflows/scripts/assignReviewers.js | 11 ++++++----- .github/workflows/scripts/getPRProperties.js | 8 ++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scripts/assignReviewers.js b/.github/workflows/scripts/assignReviewers.js index 6d18a3da0f6..8bbe50f6104 100644 --- a/.github/workflows/scripts/assignReviewers.js +++ b/.github/workflows/scripts/assignReviewers.js @@ -14,15 +14,16 @@ function pickFrom(candidates, exclude, no) { } async function assignReviewers({github, context, prData}) { - const reviewers = prData.review.reviewers.map(rv => rv.login); + const allReviewers = prData.review.reviewers.map(rv => rv.login); + const requestedReviewers = prData.review.requestedReviewers; const missingPrebidEng = prData.review.requires.prebidEngineers - prData.review.prebidEngineers; const missingPrebidReviewers = prData.review.requires.prebidReviewers - prData.review.prebidReviewers - (missingPrebidEng > 0 ? missingPrebidEng : 0); if (missingPrebidEng > 0) { - reviewers.push(...pickFrom(prData.prebidEngineers, [...reviewers, prData.author.login], missingPrebidEng)) + requestedReviewers.push(...pickFrom(prData.prebidEngineers, [...allReviewers, prData.author.login], missingPrebidEng)) } if (missingPrebidReviewers > 0) { - reviewers.push(...pickFrom(prData.prebidReviewers, [...reviewers, prData.author.login], missingPrebidReviewers)) + requestedReviewers.push(...pickFrom(prData.prebidReviewers, [...allReviewers, prData.author.login], missingPrebidReviewers)) } const request = ghRequester(github); @@ -30,9 +31,9 @@ async function assignReviewers({github, context, prData}) { owner: context.repo.owner, repo: context.repo.repo, prNo: prData.pr, - reviewers + reviewers: requestedReviewers }) - return reviewers; + return requestedReviewers; } module.exports = assignReviewers; diff --git a/.github/workflows/scripts/getPRProperties.js b/.github/workflows/scripts/getPRProperties.js index 902709db08a..f663e976ce5 100644 --- a/.github/workflows/scripts/getPRProperties.js +++ b/.github/workflows/scripts/getPRProperties.js @@ -102,12 +102,16 @@ async function getPRProperties({github, context, prNo, reviewerTeam, engTeam, au const review = { prebidEngineers: 0, prebidReviewers: 0, - reviewers: [] + reviewers: [], + requestedReviewers: [] }; const author = pr.data.user.login; const allReviewers = new Set(); pr.data.requested_reviewers - .forEach(rv => allReviewers.add(rv.login)); + .forEach(rv => { + allReviewers.add(rv.login); + review.requestedReviewers.push(rv.login); + }); prReviews.data.forEach(datum => allReviewers.add(datum.user.login)); allReviewers From 7e1a7c9e3a4e14de7826a19c74029fcfee30ce9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petric=C4=83=20Nanc=C4=83?= Date: Thu, 20 Nov 2025 21:58:35 +0200 Subject: [PATCH 137/147] sevioBidAdapter: send currency if this is set in the config (#14143) * Read currency configs * Add tests for the currency handling --- modules/sevioBidAdapter.js | 6 +++ test/spec/modules/sevioBidAdapter_spec.js | 64 ++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index 4551bd2c76c..0b34c3098dd 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -131,6 +131,11 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const userSyncEnabled = config.getConfig("userSync.syncEnabled"); + const currencyConfig = config.getConfig('currency'); + const currency = + currencyConfig?.adServerCurrency || + currencyConfig?.defaultCurrency || + null; // (!) that avoids top-level side effects (the thing that can stop registerBidder from running) const computeTTFB = (w = (typeof window !== 'undefined' ? window : undefined)) => { try { @@ -212,6 +217,7 @@ export const spec = { source: eid.source, id: eid.uids?.[0]?.id })).filter(eid => eid.source && eid.id), + ...(currency ? { currency } : {}), ads: [ { sizes: formattedSizes, diff --git a/test/spec/modules/sevioBidAdapter_spec.js b/test/spec/modules/sevioBidAdapter_spec.js index a7a56d798a9..d82f0da1f6c 100644 --- a/test/spec/modules/sevioBidAdapter_spec.js +++ b/test/spec/modules/sevioBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/sevioBidAdapter.js'; - +import { config } from 'src/config.js'; const ENDPOINT_URL = 'https://req.adx.ws/prebid'; describe('sevioBidAdapter', function () { @@ -446,4 +446,66 @@ describe('sevioBidAdapter', function () { ]); }); }); + + describe('currency handling', function () { + let bidRequests; + let bidderRequests; + + beforeEach(function () { + bidRequests = [{ + bidder: 'sevio', + params: { zone: 'zoneId' }, + mediaTypes: { banner: { sizes: [[300, 250]] } }, + bidId: '123' + }]; + + bidderRequests = { + refererInfo: { + referer: 'https://example.com', + page: 'https://example.com', + } + }; + }); + + afterEach(function () { + if (typeof config.resetConfig === 'function') { + config.resetConfig(); + } else if (typeof config.setConfig === 'function') { + config.setConfig({ currency: null }); + } + }); + + it('includes EUR currency when EUR is set in prebid config', function () { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + + const req = spec.buildRequests(bidRequests, bidderRequests); + const payload = req[0].data; + + expect(payload.currency).to.equal('EUR'); + }); + + it('includes GBP currency when GBP is set in prebid config', function () { + config.setConfig({ + currency: { + adServerCurrency: 'GBP' + } + }); + + const req = spec.buildRequests(bidRequests, bidderRequests); + const payload = req[0].data; + + expect(payload.currency).to.equal('GBP'); + }); + + it('does NOT include currency when no currency config is set', function () { + const req = spec.buildRequests(bidRequests, bidderRequests); + const payload = req[0].data; + + expect(payload).to.not.have.property('currency'); + }); + }); }); From 135ecb97f9fa18ab5aeb8f126321c66eea12efd9 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Nov 2025 16:23:26 -0500 Subject: [PATCH 138/147] CI: bump chrome 109 to 113 and move it off browserstack (#14187) * local e2e testing * rework concurrency id * clean up inputs * try installing safari * specify shell * try npx * break out build from test for e2e * try safaridriver --enable * adjust build, safaridriver * safari why such a pain * safari * safari... * do not allow parallel tests * try to install chrome 109 * log browsers * deb installation * fix deb installation * extract install-deb * fix install-deb * more flexible version definition * remove safari 15 * disable coverage on chrome 109 * Better job names * bump chrome 109 -> 113, and add logic to test it outside browserstack * reintroduce safari 15.6 * try --no-sandbox * rename custom -> noSandbox * update wait for browserstack to accept variable number of sessions * fix type error --- .github/actions/install-deb/action.yml | 35 +++++++++++++++++++ .../actions/wait-for-browserstack/action.yml | 7 ++-- .github/workflows/browser-tests.yml | 35 ++++++++++++++----- .github/workflows/browser_testing.json | 9 ++++- .github/workflows/run-tests.yml | 35 +++++++++++++++++-- browsers.json | 5 ++- karma.conf.maker.js | 15 ++++---- 7 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 .github/actions/install-deb/action.yml diff --git a/.github/actions/install-deb/action.yml b/.github/actions/install-deb/action.yml new file mode 100644 index 00000000000..c33bfb220ba --- /dev/null +++ b/.github/actions/install-deb/action.yml @@ -0,0 +1,35 @@ +name: Install deb +description: Download and install a .deb package +inputs: + url: + description: URL to the .deb file + required: true + name: + description: A local name for the package. Required if using this action multiple times in the same context. + default: package.deb + required: false + +runs: + using: 'composite' + steps: + - name: Restore deb + id: deb-restore + uses: actions/cache/restore@v4 + with: + path: "${{ runner.temp }}/${{ inputs.name }}" + key: ${{ inputs.url }} + - name: Download deb + if: ${{ steps.deb-restore.outputs.cache-hit != 'true' }} + shell: bash + run: | + wget --no-verbose "${{ inputs.url }}" -O "${{ runner.temp }}/${{ inputs.name }}" + - name: Cache deb + if: ${{ steps.deb-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v4 + with: + path: "${{ runner.temp }}/${{ inputs.name }}" + key: ${{ inputs.url }} + - name: Install deb + shell: bash + run: | + sudo apt-get install -y --allow-downgrades "${{ runner.temp }}/${{ inputs.name }}" diff --git a/.github/actions/wait-for-browserstack/action.yml b/.github/actions/wait-for-browserstack/action.yml index 12ad89d7008..3242e29fc50 100644 --- a/.github/actions/wait-for-browserstack/action.yml +++ b/.github/actions/wait-for-browserstack/action.yml @@ -1,6 +1,9 @@ name: Wait for browserstack sessions description: Wait until enough browserstack sessions have become available - +inputs: + sessions: + description: Number of sessions needed to continue + default: "6" runs: using: 'composite' steps: @@ -14,7 +17,7 @@ runs: queued=$(jq '.queued_sessions' <<< $status) max_queued=$(jq '.queued_sessions_max_allowed' <<< $status) spare=$(( ${max_running} + ${max_queued} - ${running} - ${queued} )) - required=6 + required=${{ inputs.sessions }} echo "Browserstack status: ${running} sessions running, ${queued} queued, ${spare} free" (( ${required} > ${spare} )) do diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml index 8dfc9dcf665..3e4bc947ad1 100644 --- a/.github/workflows/browser-tests.yml +++ b/.github/workflows/browser-tests.yml @@ -18,11 +18,13 @@ jobs: setup: needs: build - name: "Setup environment" + name: "Define testing strategy" runs-on: ubuntu-latest outputs: browsers: ${{ toJSON(fromJSON(steps.define.outputs.result).browsers) }} + latestBrowsers: ${{ toJSON(fromJSON(steps.define.outputs.result).latestBrowsers) }} bstack-key: ${{ steps.bstack-save.outputs.name }} + bstack-sessions: ${{ fromJSON(steps.define.outputs.result).bsBrowsers }} steps: - name: Checkout uses: actions/checkout@v5 @@ -36,23 +38,35 @@ jobs: with: script: | const fs = require('node:fs/promises'); - const browsers = require('./.github/workflows/browser_testing.json'); - const excludeFromBstack = Object.values(browsers).map(browser => browser.bsName); + const browsers = Object.entries( + require('./.github/workflows/browser_testing.json') + ).flatMap(([name, browser]) => { + browser = Object.assign({name, version: 'latest'}, browser); + const browsers = [browser]; + const versions = browser.versions; + if (versions) { + delete browser.versions; + browsers.push(...Object.entries(versions).map(([version, def]) => Object.assign({}, browser, {version, ...def}))) + } + return browsers; + }) const bstackBrowsers = Object.fromEntries( - // exlude "latest" version of browsers that we can test on GH actions + // exclude versions of browsers that we can test on GH actions Object.entries(require('./browsers.json')) - .filter(([name, def]) => !excludeFromBstack.includes(def.browser) || def.browser_version !== 'latest') + .filter(([name, def]) => browsers.find(({bsName, version}) => bsName === def.browser && version === def.browser_version) == null) ) const updatedBrowsersJson = JSON.stringify(bstackBrowsers, null, 2); console.log("Using browsers.json:", updatedBrowsersJson); + console.log("Browsers to be tested directly on runners:", JSON.stringify(browsers, null, 2)) await fs.writeFile('./browsers.json', updatedBrowsersJson); return { - hasBSBrowsers: Object.keys(bstackBrowsers).length > 0, - browsers: Object.entries(browsers).map(([name, def]) => Object.assign({name}, def)) + bsBrowsers: Object.keys(bstackBrowsers).length, + browsers, + latestBrowsers: browsers.filter(browser => browser.version === 'latest') } - name: "Save working directory" id: bstack-save - if: ${{ fromJSON(steps.define.outputs.result).hasBSBrowsers }} + if: ${{ fromJSON(steps.define.outputs.result).bsBrowsers > 0 }} uses: ./.github/actions/save with: prefix: browserstack- @@ -87,7 +101,7 @@ jobs: unit-tests: needs: [setup, build] - name: "Unit (browser: ${{ matrix.browser.name }})" + name: "Unit (browser: ${{ matrix.browser.name }} ${{ matrix.browser.version }})" strategy: fail-fast: false matrix: @@ -95,6 +109,8 @@ jobs: uses: ./.github/workflows/run-tests.yml with: + install-deb: ${{ matrix.browser.deb }} + install-chrome: ${{ matrix.browser.chrome }} built-key: ${{ needs.build.outputs.built-key }} test-cmd: npx gulp test-only-nobuild --browsers ${{ matrix.browser.name }} ${{ matrix.browser.coverage && '--coverage' || '--no-coverage' }} chunks: 8 @@ -111,6 +127,7 @@ jobs: test-cmd: npx gulp test-only-nobuild --browserstack --no-coverage chunks: 8 browserstack: true + browserstack-sessions: ${{ fromJSON(needs.setup.outputs.bstack-sessions) }} secrets: BROWSERSTACK_USER_NAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} diff --git a/.github/workflows/browser_testing.json b/.github/workflows/browser_testing.json index debfef278e1..46b0894e071 100644 --- a/.github/workflows/browser_testing.json +++ b/.github/workflows/browser_testing.json @@ -2,7 +2,14 @@ "ChromeHeadless": { "bsName": "chrome", "wdioName": "chrome", - "coverage": true + "coverage": true, + "versions": { + "113.0": { + "coverage": false, + "chrome": "113.0.5672.0", + "name": "ChromeNoSandbox" + } + } }, "EdgeHeadless": { "bsName": "edge", diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 36b1bca4b4d..74105f7a921 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -28,6 +28,11 @@ on: required: false type: boolean default: false + browserstack-sessions: + description: Number of browserstack sessions needed to run tests + required: false + type: number + default: 6 timeout: description: Timeout on test run required: false @@ -43,11 +48,19 @@ on: type: boolean required: false default: false + install-chrome: + description: Chrome version to install via @puppeteer/browsers + type: string + required: false run-npm-install: description: Run npm install before tests type: boolean required: false default: false + install-deb: + description: URL to deb to install before tests + type: string + required: false outputs: coverage: description: Artifact name for coverage results @@ -60,7 +73,7 @@ on: jobs: checkout: - name: "Set up environment" + name: "Define chunks" runs-on: ubuntu-latest outputs: chunks: ${{ steps.chunks.outputs.chunks }} @@ -87,7 +100,7 @@ jobs: matrix: chunk-no: ${{ fromJSON(needs.checkout.outputs.chunks) }} - name: "Test chunk ${{ matrix.chunk-no }}" + name: Test${{ inputs.chunks > 1 && format(' chunk {0}', matrix.chunk-no) || '' }} env: BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USER_NAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} @@ -122,7 +135,21 @@ jobs: defaults write com.apple.Safari AllowRemoteAutomation 1 sudo safaridriver --enable sudo "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" --enable - + + - name: Install Chrome + if: ${{ inputs.install-chrome }} + shell: bash + run: | + out=($(npx @puppeteer/browsers install chrome@${{ inputs.install-chrome }})) + echo 'CHROME_BIN='"${out[1]}" >> env; + cat env + cat env >> "$GITHUB_ENV" + + - name: Install deb + if: ${{ inputs.install-deb }} + uses: ./.github/actions/install-deb + with: + url: ${{ inputs.install-deb }} - name: Run npm install if: ${{ inputs.run-npm-install }} @@ -147,6 +174,8 @@ jobs: - name: 'Wait for browserstack' if: ${{ inputs.browserstack }} uses: ./.github/actions/wait-for-browserstack + with: + sessions: ${{ inputs.browserstack-sessions }} - name: Run tests uses: nick-fields/retry@v3 diff --git a/browsers.json b/browsers.json index 974df030ee7..f37d708221e 100644 --- a/browsers.json +++ b/browsers.json @@ -15,11 +15,11 @@ "device": null, "os": "Windows" }, - "bs_chrome_109_windows_10": { + "bs_chrome_113_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "109.0", + "browser_version": "113.0", "device": null, "os": "Windows" }, @@ -47,5 +47,4 @@ "device": null, "os": "OS X" } - } diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 6866b296c3e..ed91691aeb7 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -85,6 +85,12 @@ function setReporters(karmaConf, codeCoverage, browserstack, chunkNo) { } function setBrowsers(karmaConf, browserstack) { + karmaConf.customLaunchers = karmaConf.customLaunchers || {}; + karmaConf.customLaunchers.ChromeNoSandbox = { + base: 'ChromeHeadless', + // disable sandbox - necessary within Docker and when using versions installed through @puppeteer/browsers + flags: ['--no-sandbox'] + } if (browserstack) { karmaConf.browserStack = { username: process.env.BROWSERSTACK_USERNAME, @@ -100,14 +106,7 @@ function setBrowsers(karmaConf, browserstack) { } else { var isDocker = require('is-docker')(); if (isDocker) { - karmaConf.customLaunchers = karmaConf.customLaunchers || {}; - karmaConf.customLaunchers.ChromeCustom = { - base: 'ChromeHeadless', - // We must disable the Chrome sandbox when running Chrome inside Docker (Chrome's sandbox needs - // more permissions than Docker allows by default) - flags: ['--no-sandbox'] - } - karmaConf.browsers = ['ChromeCustom']; + karmaConf.browsers = ['ChromeNoSandbox']; } else { karmaConf.browsers = ['ChromeHeadless']; } From 9e07ab9162da1c207569af73db7d625d594c739c Mon Sep 17 00:00:00 2001 From: mosherBT <115997271+mosherBT@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:33:11 -0500 Subject: [PATCH 139/147] Core: fix proxy identity issue in objectGuard by caching wrapped objects (#14171) * Add weakCachec to objectGuard * add unit test * lint --------- Co-authored-by: Demetrio Girardi --- libraries/objectGuard/objectGuard.js | 17 +++++++++++++---- test/spec/activities/objectGuard_spec.js | 11 +++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/libraries/objectGuard/objectGuard.js b/libraries/objectGuard/objectGuard.js index 78ba0910bb9..f8d895be7cb 100644 --- a/libraries/objectGuard/objectGuard.js +++ b/libraries/objectGuard/objectGuard.js @@ -128,18 +128,23 @@ export function objectGuard(rules) { return true; } - function mkGuard(obj, tree, final, applies) { - return new Proxy(obj, { + 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); + } + + const proxy = new Proxy(obj, { get(target, prop, receiver) { const val = Reflect.get(target, prop, receiver); if (final && val != null && typeof val === 'object') { // a parent property has write protect rules, keep guarding - return mkGuard(val, tree, final, applies) + return mkGuard(val, tree, final, applies, cache) } else if (tree.children?.hasOwnProperty(prop)) { const {children, hasWP} = tree.children[prop]; if ((children || hasWP) && val != null && typeof val === 'object') { // some nested properties have rules, return a guard for the branch - return mkGuard(val, tree.children?.[prop] || tree, final || children == null, applies); + return mkGuard(val, tree.children?.[prop] || tree, final || children == null, applies, cache); } else if (isData(val)) { // if this property has redact rules, apply them const rule = getRedactRule(tree.children[prop]); @@ -183,6 +188,10 @@ export function objectGuard(rules) { return Reflect.deleteProperty(target, prop); } }); + + // Cache the proxy before returning + cache.set(obj, proxy); + return proxy; } return function guard(obj, ...args) { diff --git a/test/spec/activities/objectGuard_spec.js b/test/spec/activities/objectGuard_spec.js index f747a9544f7..5a86160c1f3 100644 --- a/test/spec/activities/objectGuard_spec.js +++ b/test/spec/activities/objectGuard_spec.js @@ -13,6 +13,11 @@ describe('objectGuard', () => { get(val) { return `repl${val}` }, } }) + it('should preserve object identity', () => { + 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({ @@ -97,6 +102,12 @@ describe('objectGuard', () => { }); }); + it('should preserve object identity', () => { + 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'}]); From 2d039f761f8691694c85f821741d71b10d3959b1 Mon Sep 17 00:00:00 2001 From: SvenKoster Date: Thu, 20 Nov 2025 23:59:20 +0200 Subject: [PATCH 140/147] StartioBidAdapter: Change the protocol from http to https (#14128) * Start.io adapter: Change the protocol from http to https * Start.io adapter: Changing content-type to json * Start.io adapter: Changing content-type back to text/plain --- modules/startioBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/startioBidAdapter.js b/modules/startioBidAdapter.js index 76a68f8ce95..74629f2cc9c 100644 --- a/modules/startioBidAdapter.js +++ b/modules/startioBidAdapter.js @@ -7,7 +7,7 @@ import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; const BIDDER_CODE = 'startio'; const METHOD = 'POST'; const GVLID = 1216; -const ENDPOINT_URL = `http://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`; +const ENDPOINT_URL = `https://pbc-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbc`; const converter = ortbConverter({ imp(buildImp, bidRequest, context) { From 705c1d76729dfb5e35623c8907bac36a7a561ef9 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 Nov 2025 16:59:38 -0500 Subject: [PATCH 141/147] Rename greenbids bid adapter spec file (#14191) --- .../{greenbidsBidAdapter_specs.js => greenbidsBidAdapter_spec.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/spec/modules/{greenbidsBidAdapter_specs.js => greenbidsBidAdapter_spec.js} (100%) diff --git a/test/spec/modules/greenbidsBidAdapter_specs.js b/test/spec/modules/greenbidsBidAdapter_spec.js similarity index 100% rename from test/spec/modules/greenbidsBidAdapter_specs.js rename to test/spec/modules/greenbidsBidAdapter_spec.js From adf81ba0220adc504a316fd5ea2686a0fab73c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20=C3=96str=C3=B6m?= <146713650+seenthis-alex@users.noreply.github.com> Date: Thu, 20 Nov 2025 23:40:05 +0100 Subject: [PATCH 142/147] SeenThis Brand Stories Rendering Module: initial release (fixed) (#14044) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add SeenThis Brand Stories module * test: add unit tests for seenthisBrandStories module functions and constants * remove support for loading inside iframe * only allow events of seenthis origin * fix tests; update applying of styling * sort variables * emit billable event on init --------- Co-authored-by: Per Holmäng --- modules/seenthisBrandStories.md | 10 + modules/seenthisBrandStories.ts | 161 +++++++++ .../spec/modules/seenthisBrandStories_spec.js | 333 ++++++++++++++++++ 3 files changed, 504 insertions(+) create mode 100644 modules/seenthisBrandStories.md create mode 100644 modules/seenthisBrandStories.ts create mode 100644 test/spec/modules/seenthisBrandStories_spec.js diff --git a/modules/seenthisBrandStories.md b/modules/seenthisBrandStories.md new file mode 100644 index 00000000000..8b438d8ca6c --- /dev/null +++ b/modules/seenthisBrandStories.md @@ -0,0 +1,10 @@ +# Overview + +Module Name: SeenThis Brand Stories +Maintainer: tech@seenthis.se + +# Description + +Module to allow publishers to handle SeenThis Brand Stories ads. The module will handle communication with the ad iframe and resize the ad iframe accurately and handle fullscreen mode according to product specification. + +This will allow publishers to safely run the ad format without the need to disable Safeframe when using Prebid.js. diff --git a/modules/seenthisBrandStories.ts b/modules/seenthisBrandStories.ts new file mode 100644 index 00000000000..8c6ee589272 --- /dev/null +++ b/modules/seenthisBrandStories.ts @@ -0,0 +1,161 @@ +import { EVENTS } from "../src/constants.js"; +import { getBoundingClientRect } from "../libraries/boundingClientRect/boundingClientRect.js"; +import { getWinDimensions } from "../src/utils.js"; +import * as events from "../src/events.js"; + +export const DEFAULT_MARGINS = "16px"; +export const SEENTHIS_EVENTS = [ + "@seenthis_storylines/ready", + "@seenthis_enabled", + "@seenthis_disabled", + "@seenthis_metric", + "@seenthis_detach", + "@seenthis_modal/opened", + "@seenthis_modal/closed", + "@seenthis_modal/beforeopen", + "@seenthis_modal/beforeclose", +]; + +const classNames: Record = { + container: "storylines-container", + expandedBody: "seenthis-storylines-fullscreen", +}; +const containerElements: Record = {}; +const frameElements: Record = {}; +const isInitialized: Record = {}; + +export function calculateMargins(element: HTMLElement) { + const boundingClientRect = getBoundingClientRect(element); + const wrapperLeftMargin = window.getComputedStyle(element).marginLeft; + const marginLeft = boundingClientRect.left - parseInt(wrapperLeftMargin, 10); + + if (boundingClientRect.width === 0 || marginLeft === 0) { + element.style.setProperty("--storylines-margins", DEFAULT_MARGINS); + element.style.setProperty("--storylines-margin-left", DEFAULT_MARGINS); + return; + } + element.style.setProperty("--storylines-margin-left", `-${marginLeft}px`); + element.style.setProperty("--storylines-margins", `${marginLeft * 2}px`); +} + +export function getFrameByEvent(event: MessageEvent) { + const isAncestor = (childWindow: Window, frameWindow: Window) => { + if (frameWindow === childWindow) { + return true; + } else if (childWindow === window.top) { + return false; + } + if (!childWindow?.parent) return false; + return isAncestor(childWindow.parent, frameWindow); + }; + const iframeThatMatchesSource = Array.from( + document.getElementsByTagName("iframe") + ).find((frame) => + isAncestor(event.source as Window, frame.contentWindow as Window) + ); + return iframeThatMatchesSource || null; +} + +export function addStyleToSingleChildAncestors( + element: HTMLElement, + { key, value }: { key: string; value: string } +) { + if (!element || !key) return; + if ( + key in element.style && + "offsetWidth" in element && + element.offsetWidth < getWinDimensions().innerWidth + ) { + element.style.setProperty(key, value); + } + if (!element.parentElement || element.parentElement?.children.length > 1) { + return; + } + addStyleToSingleChildAncestors(element.parentElement, { key, value }); +} + +export function findAdWrapper(target: HTMLDivElement) { + return target?.parentElement?.parentElement; +} + +export function applyFullWidth(target: HTMLDivElement) { + const adWrapper = findAdWrapper(target); + if (adWrapper) { + addStyleToSingleChildAncestors(adWrapper, { key: "width", value: "100%" }); + } +} + +export function applyAutoHeight(target: HTMLDivElement) { + const adWrapper = findAdWrapper(target); + if (adWrapper) { + addStyleToSingleChildAncestors(adWrapper, { key: "height", value: "auto" }); + addStyleToSingleChildAncestors(adWrapper, { + key: "min-height", + value: "auto", + }); + } +} + +// listen to messages from iframes +window.addEventListener("message", (event) => { + if (!["https://video.seenthis.se"].includes(event?.origin)) return; + + const data = event?.data; + if (!data) return; + + switch (data.type) { + case "storylines:init": { + const storyKey = data.storyKey; + if (!storyKey || isInitialized[storyKey]) return; + isInitialized[storyKey] = true; + + frameElements[storyKey] = getFrameByEvent(event); + containerElements[storyKey] = frameElements[storyKey] + ?.parentElement as HTMLDivElement; + event.source?.postMessage( + "storylines:init-ok", + "*" as WindowPostMessageOptions + ); + + const styleEl = document.createElement("style"); + styleEl.textContent = data.css; + document.head.appendChild(styleEl); + if (data.fixes.includes("full-width")) { + applyFullWidth(containerElements[storyKey]); + } + if (data.fixes.includes("auto-height")) { + applyAutoHeight(containerElements[storyKey]); + } + + containerElements[storyKey]?.classList.add(classNames.container); + calculateMargins(containerElements[storyKey]); + + events.emit(EVENTS.BILLABLE_EVENT, { + vendor: "seenthis", + type: "storylines_init", + }); + break; + } + case "@seenthis_modal/beforeopen": { + const storyKey = data.detail.storyKey; + document.body.classList.add(classNames.expandedBody); + containerElements[storyKey]?.classList.add("expanded"); + break; + } + case "@seenthis_modal/beforeclose": { + const storyKey = data.detail.storyKey; + document.body.classList.remove(classNames.expandedBody); + containerElements[storyKey]?.classList.remove("expanded"); + break; + } + } + + // dispatch SEENTHIS_EVENTS to parent window + if (SEENTHIS_EVENTS.includes(data.type)) { + window.dispatchEvent(new CustomEvent(data.type, { detail: data })); + } +}); + +Array.from(window.frames).forEach((frame) => { + frame.postMessage("storylines:bridge-ready", "*"); +}); diff --git a/test/spec/modules/seenthisBrandStories_spec.js b/test/spec/modules/seenthisBrandStories_spec.js new file mode 100644 index 00000000000..c565a33fa88 --- /dev/null +++ b/test/spec/modules/seenthisBrandStories_spec.js @@ -0,0 +1,333 @@ +import { expect } from "chai"; +import { + addStyleToSingleChildAncestors, + applyAutoHeight, + applyFullWidth, + calculateMargins, + DEFAULT_MARGINS, + findAdWrapper, + getFrameByEvent, + SEENTHIS_EVENTS, +} from "modules/seenthisBrandStories.ts"; +import * as boundingClientRect from "../../../libraries/boundingClientRect/boundingClientRect.js"; +import * as utils from "../../../src/utils.js"; +import * as winDimensions from "src/utils/winDimensions.js"; + +describe("seenthisBrandStories", function () { + describe("constants", function () { + it("should have correct DEFAULT_MARGINS", function () { + expect(DEFAULT_MARGINS).to.equal("16px"); + }); + + it("should have correct SEENTHIS_EVENTS array", function () { + expect(SEENTHIS_EVENTS).to.be.an("array").with.length(9); + expect(SEENTHIS_EVENTS).to.include("@seenthis_storylines/ready"); + expect(SEENTHIS_EVENTS).to.include("@seenthis_enabled"); + expect(SEENTHIS_EVENTS).to.include("@seenthis_modal/opened"); + }); + }); + + describe("calculateMargins", function () { + let mockElement; + let getBoundingClientRectStub; + let getComputedStyleStub; + + beforeEach(function () { + mockElement = { + style: { + setProperty: sinon.stub(), + }, + }; + + getBoundingClientRectStub = sinon.stub( + boundingClientRect, + "getBoundingClientRect" + ); + getComputedStyleStub = sinon.stub(window, "getComputedStyle"); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should set margins correctly with non-zero values", function () { + getBoundingClientRectStub.returns({ left: 32, width: 300 }); + getComputedStyleStub.returns({ marginLeft: "16px" }); + + calculateMargins(mockElement); + + expect( + mockElement.style.setProperty.calledWith( + "--storylines-margin-left", + "-16px" + ) + ).to.be.true; + expect( + mockElement.style.setProperty.calledWith("--storylines-margins", "32px") + ).to.be.true; + }); + + it("should use default margins when width is 0", function () { + getBoundingClientRectStub.returns({ left: 16, width: 0 }); + getComputedStyleStub.returns({ marginLeft: "0px" }); + + calculateMargins(mockElement); + + expect( + mockElement.style.setProperty.calledWith("--storylines-margins", "16px") + ).to.be.true; + expect( + mockElement.style.setProperty.calledWith( + "--storylines-margin-left", + "16px" + ) + ).to.be.true; + }); + + it("should use default margins when margin left is 0", function () { + getBoundingClientRectStub.returns({ left: 16, width: 300 }); + getComputedStyleStub.returns({ marginLeft: "16px" }); + + calculateMargins(mockElement); + + expect( + mockElement.style.setProperty.calledWith("--storylines-margins", "16px") + ).to.be.true; + expect( + mockElement.style.setProperty.calledWith( + "--storylines-margin-left", + "16px" + ) + ).to.be.true; + }); + }); + + describe("getFrameByEvent", function () { + let getElementsByTagNameStub; + let mockIframes; + let mockEventSource; + + beforeEach(function () { + mockEventSource = { id: "frame2" }; + + mockIframes = [ + { contentWindow: { id: "frame1" } }, + { contentWindow: mockEventSource }, // This will match + { contentWindow: { id: "frame3" } }, + ]; + + getElementsByTagNameStub = sinon + .stub(document, "getElementsByTagName") + .returns(mockIframes); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should return iframe matching event source", function () { + const mockEvent = { + source: mockEventSource, // This should match mockIframes[1].contentWindow + }; + + const result = getFrameByEvent(mockEvent); + + expect(result).to.equal(mockIframes[1]); + expect(getElementsByTagNameStub.calledWith("iframe")).to.be.true; + }); + + it("should return undefined if no iframe matches", function () { + const mockEvent = { + source: { id: "nonexistent" }, // This won't match any iframe + }; + + const result = getFrameByEvent(mockEvent); + + expect(result).to.be.null; + }); + }); + + describe("addStyleToSingleChildAncestors", function () { + beforeEach(function () { + sinon + .stub(winDimensions, "getWinDimensions") + .returns({ innerWidth: 1024, innerHeight: 768 }); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should apply style to element when width is less than window width", function () { + const mockElement = { + style: { + setProperty: sinon.stub(), + width: "", // key exists + }, + offsetWidth: 400, + parentElement: null, + }; + + addStyleToSingleChildAncestors(mockElement, { + key: "width", + value: "100%", + }); + + expect(mockElement.style.setProperty.calledWith("width", "100%")).to.be + .true; + }); + + it("should not apply style when element width equals window width", function () { + const mockElement = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 1024, + parentElement: null, + }; + + addStyleToSingleChildAncestors(mockElement, { + key: "width", + value: "100%", + }); + + expect(mockElement.style.setProperty.called).to.be.false; + }); + + it("should recursively apply to single child ancestors", function () { + const grandParent = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 800, + parentElement: null, + children: { length: 1 }, + }; + + const parent = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 600, + parentElement: grandParent, + children: { length: 1 }, + }; + + const child = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 400, + parentElement: parent, + }; + + addStyleToSingleChildAncestors(child, { key: "width", value: "100%" }); + + expect(child.style.setProperty.calledWith("width", "100%")).to.be.true; + expect(parent.style.setProperty.calledWith("width", "100%")).to.be.true; + expect(grandParent.style.setProperty.calledWith("width", "100%")).to.be + .true; + }); + + it("should stop recursion when parent has multiple children", function () { + const parent = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 600, + parentElement: null, + children: { length: 2 }, // Multiple children + }; + + const child = { + style: { + setProperty: sinon.stub(), + width: "", + }, + offsetWidth: 400, + parentElement: parent, + }; + + addStyleToSingleChildAncestors(child, { key: "width", value: "100%" }); + + expect(child.style.setProperty.calledWith("width", "100%")).to.be.true; + expect(parent.style.setProperty.called).to.be.false; + }); + + it("should not apply style when key is not in element style", function () { + const mockElement = { + style: { + setProperty: sinon.stub(), + // 'width' key not present + }, + offsetWidth: 400, + parentElement: null, + }; + + addStyleToSingleChildAncestors(mockElement, { + key: "width", + value: "100%", + }); + + expect(mockElement.style.setProperty.called).to.be.false; + }); + }); + + describe("findAdWrapper", function () { + it("should return grandparent element", function () { + const grandParent = {}; + const parent = { parentElement: grandParent }; + const target = { parentElement: parent }; + + const result = findAdWrapper(target); + + expect(result).to.equal(grandParent); + }); + }); + + describe("applyFullWidth", function () { + let findAdWrapperStub; + let addStyleToSingleChildAncestorsStub; + + beforeEach(function () { + findAdWrapperStub = sinon.stub(); + addStyleToSingleChildAncestorsStub = sinon.stub(); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should call addStyleToSingleChildAncestors with width 100% when adWrapper exists", function () { + const mockTarget = {}; + + expect(() => applyFullWidth(mockTarget)).to.not.throw(); + }); + + it("should handle null adWrapper gracefully", function () { + const mockTarget = {}; + + expect(() => applyFullWidth(mockTarget)).to.not.throw(); + }); + }); + + describe("applyAutoHeight", function () { + it("should call addStyleToSingleChildAncestors with height auto when adWrapper exists", function () { + const mockTarget = {}; + + // Test that function executes without errors + expect(() => applyAutoHeight(mockTarget)).to.not.throw(); + }); + + it("should handle null adWrapper gracefully", function () { + const mockTarget = {}; + + expect(() => applyAutoHeight(mockTarget)).to.not.throw(); + }); + }); +}); From d90038742037d2c9fc4ddad1c18310f6a0c8fce3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 21 Nov 2025 10:19:34 -0500 Subject: [PATCH 143/147] Various modules: fix tests (#14194) * GreenbidsBidAdapter: fix tests * fix adnuntius & pbsBidAdapter --- modules/greenbidsBidAdapter.js | 2 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 1 + test/spec/modules/greenbidsBidAdapter_spec.js | 171 ++++++------------ .../modules/prebidServerBidAdapter_spec.js | 14 +- 4 files changed, 65 insertions(+), 123 deletions(-) diff --git a/modules/greenbidsBidAdapter.js b/modules/greenbidsBidAdapter.js index 2b5a0790459..490eb9e703d 100644 --- a/modules/greenbidsBidAdapter.js +++ b/modules/greenbidsBidAdapter.js @@ -10,7 +10,7 @@ import { getReferrerInfo, getPageTitle, getPageDescription, getConnectionDownLin */ const BIDDER_CODE = 'greenbids'; -const ENDPOINT_URL = 'https://hb.greenbids.ai'; +export const ENDPOINT_URL = 'https://hb.greenbids.ai'; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 8a531ba08db..6c6f08b8750 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -1,3 +1,4 @@ +import '../../../src/prebid.js'; import {expect} from 'chai'; import {spec} from 'modules/adnuntiusBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; diff --git a/test/spec/modules/greenbidsBidAdapter_spec.js b/test/spec/modules/greenbidsBidAdapter_spec.js index 61e9c1d4e17..4cf992434dc 100644 --- a/test/spec/modules/greenbidsBidAdapter_spec.js +++ b/test/spec/modules/greenbidsBidAdapter_spec.js @@ -1,11 +1,46 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec } from 'modules/greenbidsBidAdapter.js'; +import { spec, ENDPOINT_URL } from 'modules/greenbidsBidAdapter.js'; import { getScreenOrientation } from 'src/utils.js'; -const ENDPOINT = 'https://d.greenbids.ai/hb/bid-request'; const AD_SCRIPT = '"'; describe('greenbidsBidAdapter', () => { + const bidderRequestDefault = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + const bidRequests = [ + { + 'bidder': 'greenbids', + 'params': { + 'placementId': 4242 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + } + ]; + + function checkMediaTypesSizes(mediaTypes, expectedSizes) { + const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); + const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderRequestDefault); + const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); + + return payloadWithBannerSizes.data.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); + } + const adapter = newBidder(spec); let sandbox; @@ -62,7 +97,7 @@ describe('greenbidsBidAdapter', () => { it('should send bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); - expect(request.url).to.equal(ENDPOINT); + expect(request.url).to.equal(ENDPOINT_URL); expect(request.method).to.equal('POST'); }); @@ -319,74 +354,6 @@ describe('greenbidsBidAdapter', () => { expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); }); - - it('should add hardwareConcurrency info to payload', function () { - const originalHardwareConcurrency = window.top.navigator.hardwareConcurrency; - - const mockHardwareConcurrency = (value) => { - Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { - value, - configurable: true, - }); - }; - - try { - const mockValue = 8; - mockHardwareConcurrency(mockValue); - - const requestWithHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithHardwareConcurrency = JSON.parse(requestWithHardwareConcurrency.data); - - expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.exist; - expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.deep.equal(mockValue); - - mockHardwareConcurrency(undefined); - - const requestWithoutHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithoutHardwareConcurrency = JSON.parse(requestWithoutHardwareConcurrency.data); - - expect(payloadWithoutHardwareConcurrency.hardwareConcurrency).to.not.exist; - } finally { - Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { - value: originalHardwareConcurrency, - configurable: true, - }); - } - }); - - it('should add deviceMemory info to payload', function () { - const originalDeviceMemory = window.top.navigator.deviceMemory; - - const mockDeviceMemory = (value) => { - Object.defineProperty(window.top.navigator, 'deviceMemory', { - value, - configurable: true, - }); - }; - - try { - const mockValue = 4; - mockDeviceMemory(mockValue); - - const requestWithDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithDeviceMemory = JSON.parse(requestWithDeviceMemory.data); - - expect(payloadWithDeviceMemory.deviceMemory).to.exist; - expect(payloadWithDeviceMemory.deviceMemory).to.deep.equal(mockValue); - - mockDeviceMemory(undefined); - - const requestWithoutDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); - const payloadWithoutDeviceMemory = JSON.parse(requestWithoutDeviceMemory.data); - - expect(payloadWithoutDeviceMemory.deviceMemory).to.not.exist; - } finally { - Object.defineProperty(window.top.navigator, 'deviceMemory', { - value: originalDeviceMemory, - configurable: true, - }); - } - }); }); describe('pageTitle', function () { @@ -690,14 +657,20 @@ describe('greenbidsBidAdapter', () => { it('should add schain info to payload if available', function () { const bidRequest = Object.assign({}, bidRequests[0], { - schain: { - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'example.com', - sid: '00001', - hp: 1 - }] + ortb2: { + source: { + ext: { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + } + } + } } }); @@ -904,6 +877,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'currency': 'USD', 'height': 250, + 'size': '200x100', 'bidId': '3ede2a3fa0db94', 'ttl': 360, 'width': 300, @@ -914,6 +888,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'currency': 'USD', 'height': 200, + 'size': '300x150', 'bidId': '4fef3b4gb1ec15', 'ttl': 360, 'width': 350, @@ -939,6 +914,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'width': 300, 'height': 250, + 'size': '200x100', 'currency': 'USD', 'netRevenue': true, 'meta': { @@ -953,6 +929,7 @@ describe('greenbidsBidAdapter', () => { 'cpm': 0.5, 'width': 350, 'height': 200, + 'size': '300x150', 'currency': 'USD', 'netRevenue': true, 'meta': { @@ -993,39 +970,3 @@ describe('greenbidsBidAdapter', () => { }); }); }); - -const bidderRequestDefault = { - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000 -}; - -const bidRequests = [ - { - 'bidder': 'greenbids', - 'params': { - 'placementId': 4242 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee', - 'deviceWidth': 1680 - } -]; - -function checkMediaTypesSizes(mediaTypes, expectedSizes) { - const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); - const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderRequestDefault); - const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); - - return payloadWithBannerSizes.data.forEach(bid => { - if (Array.isArray(expectedSizes)) { - expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); - } else { - expect(bid.sizes[0]).to.equal(expectedSizes); - } - }); -} diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 882f38ba2a9..595b95c6db8 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -7,7 +7,7 @@ import { } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager, {PBS_ADAPTER_NAME} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {deepAccess, deepClone, mergeDeep} from 'src/utils.js'; +import {deepAccess, deepClone, getWinDimensions, mergeDeep} from 'src/utils.js'; import {ajax} from 'src/ajax.js'; import {config} from 'src/config.js'; import * as events from 'src/events.js'; @@ -1195,8 +1195,8 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.screen.width, - h: window.screen.height, + w: getWinDimensions().screen.width, + h: getWinDimensions().screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1227,8 +1227,8 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.screen.width, - h: window.screen.height, + w: getWinDimensions().screen.width, + h: getWinDimensions().screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1619,8 +1619,8 @@ describe('S2S Adapter', function () { adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { - w: window.screen.width, - h: window.screen.height, + w: getWinDimensions().screen.width, + h: getWinDimensions().screen.height, }) expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); From 72ca897934c1e9194228f931e841c869ee02d9b9 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 21 Nov 2025 10:20:06 -0500 Subject: [PATCH 144/147] Set localIdentifier for browserstack tests (#14195) --- karma.conf.maker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/karma.conf.maker.js b/karma.conf.maker.js index ed91691aeb7..f6f9d903d58 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -97,7 +97,7 @@ function setBrowsers(karmaConf, browserstack) { accessKey: process.env.BROWSERSTACK_ACCESS_KEY, build: process.env.BROWSERSTACK_BUILD_NAME } - if (process.env.TRAVIS) { + if (process.env.BROWSERSTACK_LOCAL_IDENTIFIER) { karmaConf.browserStack.startTunnel = false; karmaConf.browserStack.tunnelIdentifier = process.env.BROWSERSTACK_LOCAL_IDENTIFIER; } From 4519cd17d33114b024715c647d29dbf9e505bb33 Mon Sep 17 00:00:00 2001 From: Screencore Developer Date: Fri, 21 Nov 2025 17:27:33 +0200 Subject: [PATCH 145/147] Screencore Bid Adapter: add endpointId parameter (#14169) * Screencore prebid adapter * rearrange code * use lowercase screncore bidder code * fix tests * update tests * trigger CI * Screencore Bid Adapter: add endpointId parameter * Updated adapter to use teqblazeUtils library * Added endpointId parameter support in test parameters * Updated test specs to include endpointId validation * Screencore Bid Adapter: update sync URL to base domain Update SYNC_URL constant to use base domain. The getUserSyncs function from teqblazeUtils will append the appropriate path. * Screencore Bid Adapter: migrate to teqblazeUtils library - Update imports to use buildRequestsBase, interpretResponse, getUserSyncs, isBidRequestValid, and buildPlacementProcessingFunction from teqblazeUtils - Remove storage manager dependency (no longer needed) - Update isBidRequestValid to use placementId/endpointId params validation - Refactor buildRequests to use buildRequestsBase pattern - Rewrite test suite to match teqblazeUtils API: - Simplify test data structures - Update server response format (body as array) - Add tests for placementId/endpointId validation - Update getUserSyncs URL format expectations --------- Co-authored-by: Kostiantyn Karchevsky Co-authored-by: Demetrio Girardi Co-authored-by: Patrick McCann --- modules/screencoreBidAdapter.js | 28 +- modules/screencoreBidAdapter.md | 3 +- .../spec/modules/screencoreBidAdapter_spec.js | 905 +++++------------- 3 files changed, 271 insertions(+), 665 deletions(-) diff --git a/modules/screencoreBidAdapter.js b/modules/screencoreBidAdapter.js index ac6f5895751..22f71cf1379 100644 --- a/modules/screencoreBidAdapter.js +++ b/modules/screencoreBidAdapter.js @@ -1,16 +1,17 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; import { - createBuildRequestsFn, - createInterpretResponseFn, - createUserSyncGetter, isBidRequestValid, -} from '../libraries/vidazooUtils/bidderUtils.js'; + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'screencore'; const GVLID = 1473; const BIDDER_VERSION = '1.0.0'; +const SYNC_URL = 'https://cs.screencore.io'; const REGION_SUBDOMAIN_SUFFIX = { EU: 'taqeu', US: 'taqus', @@ -48,32 +49,29 @@ function getRegionSubdomainSuffix() { } } -export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); - export function createDomain() { const subDomain = getRegionSubdomainSuffix(); return `https://${subDomain}.screencore.io`; } -const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); +const AD_URL = `${createDomain()}/prebid`; -const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const placementProcessingFunction = buildPlacementProcessingFunction(); -const getUserSyncs = createUserSyncGetter({ - iframeSyncUrl: 'https://cs.screencore.io/api/sync/iframe', - imageSyncUrl: 'https://cs.screencore.io/api/sync/image', -}); +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid, + isBidRequestValid: isBidRequestValid(), buildRequests, interpretResponse, - getUserSyncs, + getUserSyncs: getUserSyncs(SYNC_URL), }; registerBidder(spec); diff --git a/modules/screencoreBidAdapter.md b/modules/screencoreBidAdapter.md index 60dc9b9ab21..8e5d9e3d3da 100644 --- a/modules/screencoreBidAdapter.md +++ b/modules/screencoreBidAdapter.md @@ -27,7 +27,8 @@ var adUnits = [ param1: 'loremipsum', param2: 'dolorsitamet' }, - placementId: 'testBanner' + placementId: 'testBanner', + endpointId: 'testEndpoint' } } ] diff --git a/test/spec/modules/screencoreBidAdapter_spec.js b/test/spec/modules/screencoreBidAdapter_spec.js index 4e9177e8ce5..bd1aad95edf 100644 --- a/test/spec/modules/screencoreBidAdapter_spec.js +++ b/test/spec/modules/screencoreBidAdapter_spec.js @@ -1,789 +1,396 @@ import { expect } from 'chai'; -import { createDomain, spec as adapter, storage } from 'modules/screencoreBidAdapter.js'; -import { getGlobal } from 'src/prebidGlobal.js'; -import { - extractCID, - extractPID, - extractSubDomain, - getStorageItem, - getUniqueDealId, - hashCode, - setStorageItem, - tryParseJSON, -} from 'libraries/vidazooUtils/bidderUtils.js'; +import { createDomain, spec as adapter } from 'modules/screencoreBidAdapter.js'; import { config } from 'src/config.js'; import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; -import { version } from 'package.json'; -import * as utils from 'src/utils.js'; -import sinon, { useFakeTimers } from 'sinon'; - -export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'netId', 'tdid', 'pubProvidedId', 'intentIqId', 'liveIntentId']; - -const SUB_DOMAIN = 'exchange'; +import sinon from 'sinon'; 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' - }, - 'placementId': 'testBanner' + bidId: '2d52001cabd527', + bidder: 'screencore', + adUnitCode: 'div-gpt-ad-12345-0', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + params: { + placementId: 'testPlacement', + endpointId: 'testEndpoint' }, - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [[300, 250], [300, 600]], - 'bidderRequestId': '1fdb5ff1b6eaa7', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER], - 'ortb2Imp': { - 'ext': { - 'gpid': '0123456789', - 'tid': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf' + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + ortb2Imp: { + ext: { + gpid: '0123456789', + tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf' } } }; const VIDEO_BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', - 'bidderRequestId': '12a8ae9ada9c13', - 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '635509f7ff6642d368cb9837', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1, - 'placementId': 'testBanner' + bidId: '2d52001cabd528', + bidder: 'screencore', + adUnitCode: 'video-ad-unit', + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + params: { + placementId: 'testVideoPlacement', + endpointId: 'testVideoEndpoint' }, - '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 + mediaTypes: { + video: { + playerSize: [[545, 307]], + context: 'instream', + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 3, 5, 6], + maxduration: 60, + minduration: 0, + startdelay: 0, + linearity: 1, + api: [2], + placement: 1 } }, - 'ortb2Imp': { - 'ext': { - 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + ortb2Imp: { + ext: { + tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee' } } -} - -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': '' +}; + +const NATIVE_BID = { + bidId: '2d52001cabd529', + bidder: 'screencore', + adUnitCode: 'native-ad-unit', + transactionId: '77e184c6-bde9-497b-b9b9-cf47a61381ee', + params: { + placementId: 'testNativePlacement' }, - 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'}, + mediaTypes: { + native: { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: false } + } + } }; 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' + refererInfo: { + page: 'https://www.example.com', + ref: 'https://www.referrer.com' }, - 'ortb2': { - 'site': { - 'content': { - 'language': 'en' - } - }, - 'regs': { - 'gpp': 'gpp_string', - 'gpp_sid': [7], - 'coppa': 0 - }, - 'device': ORTB2_DEVICE, + ortb2: { + device: { + w: 1920, + h: 1080, + language: 'en' + } } }; 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' - }] - }] - } + body: [{ + requestId: '2d52001cabd527', + cpm: 0.8, + creativeId: '12610997325162499419', + ttl: 30, + currency: 'USD', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + adomain: ['securepubads.g.doubleclick.net'] + }] }; const VIDEO_SERVER_RESPONSE = { - body: { - 'cid': '635509f7ff6642d368cb9837', - 'results': [{ - 'ad': '', - 'advertiserDomains': ['screencore.io'], - '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"} - } + body: [{ + requestId: '2d52001cabd528', + cpm: 2, + creativeId: '12610997325162499419', + ttl: 60, + currency: 'USD', + width: 545, + height: 307, + mediaType: 'video', + vastXml: '', + adomain: ['screencore.io'] + }] }; const REQUEST = { data: { - width: 300, - height: 250, - bidId: '2d52001cabd527' + placements: [{ + bidId: '2d52001cabd527', + adFormat: 'banner', + sizes: [[300, 250], [300, 600]] + }] } }; -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - describe('screencore bid adapter', function () { before(() => config.resetConfig()); after(() => config.resetConfig()); describe('validate spec', function () { - it('exists and is a function', function () { + it('should have isBidRequestValid as a function', function () { expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); }); - it('exists and is a function', function () { + it('should have buildRequests as a function', function () { expect(adapter.buildRequests).to.exist.and.to.be.a('function'); }); - it('exists and is a function', function () { + it('should have interpretResponse as a function', function () { expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); }); - it('exists and is a function', function () { + it('should have getUserSyncs as a function', function () { expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); }); - it('exists and is a string', function () { + it('should have code as a string', function () { expect(adapter.code).to.exist.and.to.be.a('string'); + expect(adapter.code).to.equal('screencore'); }); - it('exists and contains media types', function () { + it('should have supportedMediaTypes with BANNER, VIDEO, NATIVE', function () { expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(3); expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO, NATIVE]); }); + + it('should have gvlid', function () { + expect(adapter.gvlid).to.exist.and.to.equal(1473); + }); + + it('should have version', function () { + expect(adapter.version).to.exist.and.to.equal('1.0.0'); + }); }); describe('validate bid requests', function () { - it('should require cId', function () { + it('should return false when placementId and endpointId are missing', function () { const isValid = adapter.isBidRequestValid({ - params: { - pId: 'pid', - }, + bidId: '123', + params: {}, + mediaTypes: { banner: { sizes: [[300, 250]] } } }); expect(isValid).to.be.false; }); - it('should require pId', function () { + it('should return false when mediaTypes is missing', function () { const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - }, + bidId: '123', + params: { placementId: 'test' } }); expect(isValid).to.be.false; }); - it('should validate correctly', function () { + it('should return true when placementId is present with banner mediaType', function () { const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - pId: 'pid', - }, + bidId: '123', + params: { placementId: 'test' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }); + expect(isValid).to.be.true; + }); + + it('should return true when endpointId is present with banner mediaType', function () { + const isValid = adapter.isBidRequestValid({ + bidId: '123', + params: { endpointId: 'test' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }); + expect(isValid).to.be.true; + }); + + it('should return true when placementId is present with video mediaType', function () { + const isValid = adapter.isBidRequestValid({ + bidId: '123', + params: { placementId: 'test' }, + mediaTypes: { video: { playerSize: [[640, 480]] } } + }); + expect(isValid).to.be.true; + }); + + it('should return true when placementId is present with native mediaType', function () { + const isValid = adapter.isBidRequestValid({ + bidId: '123', + params: { placementId: 'test' }, + mediaTypes: { native: { title: { required: true } } } }); expect(isValid).to.be.true; }); }); describe('build requests', function () { - let sandbox; - before(function () { - getGlobal().bidderSettings = { - screencore: { - storageAllowed: true, - }, - }; - sandbox = sinon.createSandbox(); - sandbox.stub(Date, 'now').returns(1000); + it('should build banner request', function () { + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.exist; + expect(requests.method).to.equal('POST'); + expect(requests.url).to.include('screencore.io/prebid'); + expect(requests.data).to.exist; + expect(requests.data.placements).to.be.an('array'); + expect(requests.data.placements[0].bidId).to.equal(BID.bidId); + expect(requests.data.placements[0].adFormat).to.equal(BANNER); }); 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()}/prebid/multi/635509f7ff6642d368cb9837`, - data: { - adUnitCode: '63550ad1ff6642d368cba59dh5884270560', - bidFloor: 0.1, - bidId: '2d52001cabd527', - bidderVersion: adapter.version, - bidderRequestId: '12a8ae9ada9c13', - cb: 1000, - gdpr: 1, - gdprConsent: 'consent_string', - usPrivacy: 'consent_string', - gppString: 'gpp_string', - gppSid: [7], - prebidVersion: version, - transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - publisherId: '59ac17c192832d0011283fe3', - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - res: `${window.top.screen.width}x${window.top.screen.height}`, - schain: VIDEO_BID.schain, - sizes: ['545x307'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, - { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - 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: [], - ortb2Imp: VIDEO_BID.ortb2Imp, - ortb2: ORTB2_OBJ, - placementId: "testBanner", - userData: [], - coppa: 0 - } - }); + expect(requests).to.exist; + expect(requests.method).to.equal('POST'); + expect(requests.data.placements).to.be.an('array'); + expect(requests.data.placements[0].bidId).to.equal(VIDEO_BID.bidId); + expect(requests.data.placements[0].adFormat).to.equal(VIDEO); }); - it('should build banner request for each size', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); - config.setConfig({ - bidderTimeout: 3000 - }); - const requests = adapter.buildRequests([BID], BIDDER_REQUEST); - expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, - data: { - gdprConsent: 'consent_string', - gdpr: 1, - gppString: 'gpp_string', - gppSid: [7], - usPrivacy: 'consent_string', - transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - bidderRequestId: '1fdb5ff1b6eaa7', - sizes: ['300x250', '300x600'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, - { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - 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, - placementId: "testBanner", - userData: [], - coppa: 0 - } - }); + it('should build native request', function () { + const requests = adapter.buildRequests([NATIVE_BID], BIDDER_REQUEST); + expect(requests).to.exist; + expect(requests.data.placements).to.be.an('array'); + expect(requests.data.placements[0].bidId).to.equal(NATIVE_BID.bidId); + expect(requests.data.placements[0].adFormat).to.equal(NATIVE); }); - after(function () { - getGlobal().bidderSettings = {}; - sandbox.restore(); + it('should include gpid when available', function () { + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests.data.placements[0].gpid).to.equal('0123456789'); }); - }); - describe('getUserSyncs', function () { - it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + it('should include placementId in placement when present', function () { + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests.data.placements[0].placementId).to.equal('testPlacement'); + expect(requests.data.placements[0].type).to.equal('publisher'); + }); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.screencore.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - }]); + it('should include endpointId in placement when placementId is not present', function () { + const bidWithEndpoint = { + bidId: '2d52001cabd530', + bidder: 'screencore', + adUnitCode: 'div-gpt-ad-endpoint', + transactionId: 'd881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + params: { + endpointId: 'testEndpointOnly' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + const requests = adapter.buildRequests([bidWithEndpoint], BIDDER_REQUEST); + expect(requests.data.placements[0].endpointId).to.equal('testEndpointOnly'); + expect(requests.data.placements[0].type).to.equal('network'); }); + }); - it('should have valid user sync with cid on response', function () { + describe('getUserSyncs', function () { + it('should return iframe sync when iframeEnabled', function () { + config.setConfig({ coppa: 0 }); const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.screencore.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - }]); + expect(result).to.be.an('array').with.length(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.include('https://cs.screencore.io/iframe?pbjs=1'); }); - it('should have valid user sync with pixelEnabled', function () { + it('should return image sync when pixelEnabled', function () { + config.setConfig({ coppa: 0 }); const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); - - expect(result).to.deep.equal([{ - 'url': 'https://cs.screencore.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - 'type': 'image', - }]); + expect(result).to.be.an('array').with.length(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.include('https://cs.screencore.io/image?pbjs=1'); }); - it('should have valid user sync with coppa 1 on response', function () { - config.setConfig({ - coppa: 1, - }); + it('should include coppa parameter', function () { + config.setConfig({ coppa: 1 }); const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.screencore.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1', - }]); + expect(result[0].url).to.include('coppa=1'); }); - it('should generate url with consent data', function () { + it('should include gdpr consent when provided', function () { + config.setConfig({ coppa: 0 }); const gdprConsent = { gdprApplies: true, - consentString: 'consent_string', + consentString: 'consent_string' }; - const uspConsent = 'usp_string'; + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE], gdprConsent); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=consent_string'); + }); + + it('should include gpp consent when provided', function () { + config.setConfig({ coppa: 0 }); const gppConsent = { gppString: 'gpp_string', - applicableSections: [7], + applicableSections: [7] }; - - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); - - expect(result).to.deep.equal([{ - 'url': 'https://cs.screencore.io/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', - }]); + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE], null, null, gppConsent); + expect(result[0].url).to.include('gpp=gpp_string'); + expect(result[0].url).to.include('gpp_sid=7'); }); }); 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' }); + it('should return empty array when body is empty array', function () { + const responses = adapter.interpretResponse({ body: [] }); 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: ['screencore.io'], - agencyName: 'Agency Name', - }; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses[0].meta).to.deep.equal({ - advertiserDomains: ['screencore.io'], - agencyName: 'Agency Name', - }); + expect(responses[0].requestId).to.equal('2d52001cabd527'); + expect(responses[0].cpm).to.equal(0.8); + expect(responses[0].width).to.equal(300); + expect(responses[0].height).to.equal(250); + expect(responses[0].creativeId).to.equal('12610997325162499419'); + expect(responses[0].currency).to.equal('USD'); + expect(responses[0].ttl).to.equal(30); + expect(responses[0].ad).to.equal(''); + expect(responses[0].meta.advertiserDomains).to.deep.equal(['securepubads.g.doubleclick.net']); }); 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: ['screencore.io'], - }, - }); - }); - - it('should take default TTL', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - delete serverResponse.body.results[0].exp; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0].ttl).to.equal(300); + expect(responses[0].requestId).to.equal('2d52001cabd528'); + expect(responses[0].cpm).to.equal(2); + expect(responses[0].width).to.equal(545); + expect(responses[0].height).to.equal(307); + expect(responses[0].mediaType).to.equal('video'); + expect(responses[0].vastXml).to.equal(''); }); }); - 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); + describe('createDomain test', function () { + it('should return correct domain for US timezone', function () { + const stub = sinon.stub(Intl, 'DateTimeFormat').returns({ + resolvedOptions: () => ({ timeZone: 'America/New_York' }) }); - }); - // 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 = { - screencore: { - storageAllowed: true, - }, - }; - }); - after(function () { - getGlobal().bidderSettings = {}; - }); - const key = 'myKey'; - let uniqueDealId; - beforeEach(() => { - uniqueDealId = getUniqueDealId(storage, key, 0); - }); + const domain = createDomain(); + expect(domain).to.equal('https://taqus.screencore.io'); - 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); + stub.restore(); }); - }); - describe('storage utils', function () { - before(function () { - getGlobal().bidderSettings = { - screencore: { - 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, + it('should return correct domain for EU timezone', function () { + const stub = sinon.stub(Intl, 'DateTimeFormat').returns({ + resolvedOptions: () => ({ timeZone: 'Europe/London' }) }); - 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'); - }); + const domain = createDomain(); + expect(domain).to.equal('https://taqeu.screencore.io'); - 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); + stub.restore(); }); - }); - describe('createDomain test', function () { - it('should return correct domain', function () { + it('should return correct domain for APAC timezone', function () { const stub = sinon.stub(Intl, 'DateTimeFormat').returns({ - resolvedOptions: () => ({ timeZone: 'America/New_York' }), + resolvedOptions: () => ({ timeZone: 'Asia/Tokyo' }) }); - const responses = createDomain(); - expect(responses).to.be.equal('https://taqus.screencore.io'); + const domain = createDomain(); + expect(domain).to.equal('https://taqapac.screencore.io'); stub.restore(); }); From b35fe2c14defff89fc7483b9e99b40f7d58a7280 Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:57:53 +0200 Subject: [PATCH 146/147] Rubicon Bid Adapter: Remove PAAPI and Privacy Sandbox support (#14197) Co-authored-by: Patrick McCann --- modules/rubiconBidAdapter.js | 16 +------ test/spec/modules/rubiconBidAdapter_spec.js | 46 --------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 6543a6a88e1..76e973c7527 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -427,7 +427,6 @@ export const spec = { 'x_source.tid', 'l_pb_bid_id', 'p_screen_res', - 'o_ae', 'o_cdep', 'rp_floor', 'rp_secure', @@ -545,9 +544,6 @@ export const spec = { data['ppuid'] = configUserId; } - if (bidRequest?.ortb2Imp?.ext?.ae) { - data['o_ae'] = 1; - } // If the bid request contains a 'mobile' property under 'ortb2.site', add it to 'data' as 'p_site.mobile'. if (typeof bidRequest?.ortb2?.site?.mobile === 'number') { data['p_site.mobile'] = bidRequest.ortb2.site.mobile @@ -655,7 +651,7 @@ export const spec = { * @param {*} responseObj * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, * non-SRA responses return a plain BidRequest object - * @return {{fledgeAuctionConfigs: *, bids: *}} An array of bids which + * @return {*} An array of bids */ interpretResponse: function (responseObj, request) { responseObj = responseObj.body; @@ -760,15 +756,7 @@ export const spec = { return (adB.cpm || 0.0) - (adA.cpm || 0.0); }); - const fledgeAuctionConfigs = responseObj.component_auction_config?.map(config => { - return { config, bidId: config.bidId } - }); - - if (fledgeAuctionConfigs) { - return { bids, paapi: fledgeAuctionConfigs }; - } else { - return bids; - } + return bids; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (syncOptions.iframeEnabled) { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b96a5e4fd4f..a0a9c8ba194 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2890,15 +2890,6 @@ describe('the rubicon adapter', function () { expect(slotParams.kw).to.equal('a,b,c'); }); - it('should pass along o_ae param when fledge is enabled', () => { - const localBidRequest = Object.assign({}, bidderRequest.bids[0]); - localBidRequest.ortb2Imp.ext.ae = true; - - const slotParams = spec.createSlotParams(localBidRequest, bidderRequest); - - expect(slotParams['o_ae']).to.equal(1) - }); - it('should pass along desired segtaxes, but not non-desired ones', () => { const localBidderRequest = Object.assign({}, bidderRequest); localBidderRequest.refererInfo = {domain: 'bob'}; @@ -3816,43 +3807,6 @@ describe('the rubicon adapter', function () { expect(bids).to.be.lengthOf(0); }); - it('Should support recieving an auctionConfig and pass it along to Prebid', function () { - const response = { - 'status': 'ok', - 'account_id': 14062, - 'site_id': 70608, - 'zone_id': 530022, - 'size_id': 15, - 'alt_size_ids': [ - 43 - ], - 'tracking': '', - 'inventory': {}, - 'ads': [{ - 'status': 'ok', - 'cpm': 0, - 'size_id': 15 - }], - 'component_auction_config': [{ - 'random': 'value', - 'bidId': '5432' - }, - { - 'random': 'string', - 'bidId': '6789' - }] - }; - - const {bids, paapi} = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); - - expect(bids).to.be.lengthOf(1); - expect(paapi[0].bidId).to.equal('5432'); - expect(paapi[0].config.random).to.equal('value'); - expect(paapi[1].bidId).to.equal('6789'); - }); - it('should handle an error', function () { const response = { 'status': 'ok', From e91be0ec9353ee2c44c35e10a28fc0d9c1823556 Mon Sep 17 00:00:00 2001 From: nico piderman Date: Thu, 9 Oct 2025 12:16:05 +0200 Subject: [PATCH 147/147] fix bug in AmxBidAdapter userSync settings handling A bitwise `&` was being used instead of the intended `|`. --- modules/amxBidAdapter.js | 2 +- test/spec/modules/amxBidAdapter_spec.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index b1b22ec19f9..ef89ecdd40f 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -251,7 +251,7 @@ function getSyncSettings() { const all = isSyncEnabled(syncConfig.filterSettings, 'all'); if (all) { - settings.t = SYNC_IMAGE & SYNC_IFRAME; + settings.t = SYNC_IMAGE | SYNC_IFRAME; return settings; } diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index d1e88b35a18..1328a28e438 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -401,6 +401,23 @@ describe('AmxBidAdapter', () => { }, { ...base, t: 3 }, ], + [ + { + all: { + bidders: ['amx'], + }, + }, + { ...base, t: 3 }, + ], + [ + { + all: { + bidders: '*', + filter: 'include', + }, + }, + { ...base, t: 3 }, + ], [ { image: {