From b8d6ec6d84efe329fb8cd87d78488e378c210ba9 Mon Sep 17 00:00:00 2001 From: Julian Salinas Date: Sun, 3 Nov 2024 10:41:56 -0800 Subject: [PATCH 1/6] Add RoundhouseAds bidder adapter with tests --- modules/roundhouseadsBidAdapter.js | 127 ++++++++++++++++++ .../modules/roundhouseadsBidAdapter_spec.js | 80 +++++++++++ 2 files changed, 207 insertions(+) create mode 100644 modules/roundhouseadsBidAdapter.js create mode 100644 test/spec/modules/roundhouseadsBidAdapter_spec.js diff --git a/modules/roundhouseadsBidAdapter.js b/modules/roundhouseadsBidAdapter.js new file mode 100644 index 00000000000..182f41b8fb3 --- /dev/null +++ b/modules/roundhouseadsBidAdapter.js @@ -0,0 +1,127 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; + +/** + * RoundhouseAds adapter configurations + */ + +const BIDDER_CODE = 'roundhouseads'; +const BIDADAPTERVERSION = 'RHA-PREBID-2024.10.01'; +const USER_SYNC_ENDPOINT = 'https://roundhouseads.com/sync'; + +const isLocalhost = typeof window !== 'undefined' && window.location.hostname === 'localhost'; +const ENDPOINT_URL = isLocalhost + ? 'http://localhost:3000/bid' + : 'https://Rhapbjsv3-env.eba-aqkfquti.us-east-1.elasticbeanstalk.com/bid'; + +/** + * Validates a bid request for required parameters. + * @param {Object} bid - The bid object from Prebid. + * @returns {boolean} - True if the bid has required parameters, false otherwise. + */ +function isBidRequestValid(bid) { + return !!(bid.params && bid.params.publisherId && typeof bid.params.publisherId === 'string'); +} + +/** + * Constructs the bid request to send to the endpoint. + * @param {Array} validBidRequests - Validated bid requests. + * @param {Object} bidderRequest - Prebid's bidder request. + * @returns {Array} - Array of request objects. + */ +function buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + const data = { + id: bid.bidId, + publisherId: bid.params.publisherId, + placementId: bid.params.placementId || '', + currency: bid.params.currency || 'USD', + referer: bidderRequest.refererInfo?.page, + sizes: bid.mediaTypes?.banner?.sizes, + video: bid.mediaTypes?.video || null, + native: bid.mediaTypes?.native || null, + ext: { + ver: BIDADAPTERVERSION, + } + }; + + return { + method: 'POST', + url: ENDPOINT_URL, + data, + }; + }); +} + +/** + * Interprets the server response for Prebid. + * @param {Object} serverResponse - Server response from the bidder. + * @param {Object} request - The original request. + * @returns {Array} - Array of bid responses. + */ +function interpretResponse(serverResponse, request) { + const bidResponses = []; + const response = serverResponse.body; + + if (response && response.bids && Array.isArray(response.bids)) { + response.bids.forEach(bid => { + const bidResponse = { + requestId: bid.requestId, + cpm: bid.cpm || 0, + width: bid.width || 300, + height: bid.height || 250, + creativeId: bid.creativeId || 'defaultCreative', + currency: bid.currency || 'USD', + netRevenue: true, + ttl: bid.ttl || 360, + ad: bid.ad || '
Test Ad
', + mediaType: bid.mediaType || BANNER, + }; + + if (bid.mediaType === VIDEO) { + bidResponse.vastUrl = bid.vastUrl; + } else if (bid.mediaType === NATIVE) { + bidResponse.native = bid.native; + } + + bidResponses.push(bidResponse); + }); + } + + return bidResponses; +} + +/** + * Provides user sync URL based on available sync options. + * @param {Object} syncOptions - Sync options. + * @param {Array} serverResponses - Server responses. + * @param {Object} gdprConsent - GDPR consent data. + * @param {string} uspConsent - USP consent data. + * @returns {Array} - Array of user syncs. + */ +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + const gdprParams = gdprConsent + ? `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString)}` + : ''; + const uspParam = uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : ''; + + if (syncOptions.iframeEnabled) { + syncs.push({ type: 'iframe', url: `${USER_SYNC_ENDPOINT}?${gdprParams}${uspParam}` }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ type: 'image', url: `${USER_SYNC_ENDPOINT}?${gdprParams}${uspParam}` }); + } + + return syncs; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/test/spec/modules/roundhouseadsBidAdapter_spec.js b/test/spec/modules/roundhouseadsBidAdapter_spec.js new file mode 100644 index 00000000000..5a880429954 --- /dev/null +++ b/test/spec/modules/roundhouseadsBidAdapter_spec.js @@ -0,0 +1,80 @@ +import { expect } from 'chai'; +import { spec } from 'modules/roundhouseadsBidAdapter.js'; + +describe('RoundhouseAdsAdapter', function () { + function makeBid() { + return { + bidder: 'roundhouseads', + params: { + placementId: 'testPlacement', + publisherId: '123456', + }, + mediaTypes: { + banner: { sizes: [[300, 250], [728, 90]] }, + }, + adUnitCode: 'adunit-code', + bidId: 'bid123', + bidderRequestId: 'request123', + auctionId: 'auction123', + }; + } + + describe('isBidRequestValid', function () { + it('should return true when required params are found', function () { + const bid = makeBid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId is missing', function () { + const bid = makeBid(); + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should build a request with correct data', function () { + const bidRequests = [makeBid()]; + const bidderRequest = { refererInfo: { page: 'http://example.com' } }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].url).to.equal('http://localhost:3000/bid'); + expect(request[0].data.publisherId).to.equal('123456'); + expect(request[0].data.placementId).to.equal('testPlacement'); + }); + }); + + describe('interpretResponse', function () { + it('should interpret the server response correctly', function () { + const serverResponse = { + body: { + bids: [ + { + requestId: 'bid123', + cpm: 1.0, + width: 300, + height: 250, + creativeId: 'creative123', + currency: 'USD', + ttl: 360, + ad: '
Test Ad
', + }, + ], + }, + }; + const request = { data: { id: 'bid123' } }; + const result = spec.interpretResponse(serverResponse, request); + expect(result[0].requestId).to.equal('bid123'); + expect(result[0].cpm).to.equal(1.0); + expect(result[0].ad).to.equal('
Test Ad
'); + }); + }); + + describe('getUserSyncs', function () { + it('should return user sync iframe if iframeEnabled', function () { + const syncOptions = { iframeEnabled: true }; + const result = spec.getUserSyncs(syncOptions); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.contain('https://roundhouseads.com/sync'); + }); + }); +}); From 34239aaaf9b7772b5e6e1a9fca75fc2859800598 Mon Sep 17 00:00:00 2001 From: Julian Salinas Date: Sun, 3 Nov 2024 10:47:18 -0800 Subject: [PATCH 2/6] Include all local changes before rebase --- Prebid.js | 1 + .../gpt/x-domain/creative.html | 2 +- modules/roundhouseadsBidAdapter.md | 153 ++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 160000 Prebid.js create mode 100644 modules/roundhouseadsBidAdapter.md diff --git a/Prebid.js b/Prebid.js new file mode 160000 index 00000000000..d4f7d81f525 --- /dev/null +++ b/Prebid.js @@ -0,0 +1 @@ +Subproject commit d4f7d81f525a67c3ecad1713449b9bcfd49c7419 diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 63842b00882..b7ba6b36a79 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' - +