diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index ab7f96c4e604..f44b148bb830 100644 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -6,16 +6,18 @@ import {fetch} from '../src/ajax.js'; const bidderCode = 'lucead'; const bidderName = 'Lucead'; -let baseUrl = 'https://lucead.com'; -let staticUrl = 'https://s.lucead.com'; -let companionUrl = 'https://cdn.jsdelivr.net/gh/lucead/prebid-js-external-js-lucead@master/dist/prod.min.js'; -let endpointUrl = 'https://prebid.lucead.com/go'; const defaultCurrency = 'EUR'; const defaultTtl = 500; const aliases = ['adliveplus']; +const defaultRegion = 'eu'; +const domain = 'lucead.com' +let baseUrl = `https://${domain}`; +let staticUrl = `https://s.${domain}`; +let endpointUrl = baseUrl; +let companionUrl = 'https://cdn.jsdelivr.net/gh/lucead/prebid-js-external-js-lucead@master/dist/prod.min.js'; function isDevEnv() { - return location.hash.includes('prebid-dev') || location.href.startsWith('https://ayads.io/test'); + return location.hash.includes('prebid-dev'); } function isBidRequestValid(bidRequest) { @@ -27,11 +29,14 @@ export function log(msg, obj) { } function buildRequests(bidRequests, bidderRequest) { + const region = bidRequests[0]?.params?.region || defaultRegion; + endpointUrl = `https://${region}.${domain}`; + if (isDevEnv()) { baseUrl = location.origin; staticUrl = baseUrl; companionUrl = `${staticUrl}/dist/prebid-companion.js`; - endpointUrl = `${baseUrl}/go`; + endpointUrl = `${baseUrl}`; } log('buildRequests', { @@ -50,73 +55,91 @@ function buildRequests(bidRequests, bidderRequest) { getUniqueIdentifierStr, ortbConverter, deepSetValue, + is_sra: true, + region, }; - loadExternalScript(companionUrl, bidderCode, () => window.ayads_prebid && window.ayads_prebid(companionData)); + loadExternalScript(companionUrl, bidderCode, () => { + const fn = window.lucead_prebid; + return fn && fn(companionData); + }); - return bidRequests.map(bidRequest => ({ + return { method: 'POST', - url: `${endpointUrl}/prebid/sub`, + url: `${endpointUrl}/go/prebid/sra`, data: JSON.stringify({ request_id: bidderRequest.bidderRequestId, domain: location.hostname, - bid_id: bidRequest.bidId, - sizes: bidRequest.sizes, - media_types: bidRequest.mediaTypes, - fledge_enabled: bidderRequest.fledgeEnabled, - enable_contextual: bidRequest?.params?.enableContextual !== false, - enable_pa: bidRequest?.params?.enablePA !== false, - params: bidRequest.params, + bid_requests: bidRequests.map(bidRequest => { + return { + bid_id: bidRequest.bidId, + sizes: bidRequest.sizes, + media_types: bidRequest.mediaTypes, + placement_id: bidRequest.params.placementId, + }; + }), }), options: { contentType: 'text/plain', withCredentials: false }, - })); + }; } function interpretResponse(serverResponse, bidRequest) { // @see required fields https://docs.prebid.org/dev-docs/bidder-adaptor.html - const response = serverResponse.body; - const bidRequestData = JSON.parse(bidRequest.data); - - const bids = response.enable_contextual !== false ? [{ - requestId: response?.bid_id || '1', // bid request id, the bid id - cpm: response?.cpm || 0, - width: (response?.size && response?.size?.width) || 300, - height: (response?.size && response?.size?.height) || 250, - currency: response?.currency || defaultCurrency, - ttl: response?.ttl || defaultTtl, - creativeId: response.ssp ? `ssp:${response.ssp}` : (response?.ad_id || '0'), - netRevenue: response?.netRevenue || true, - ad: response?.ad || '', + const response = serverResponse?.body; + const bidRequestData = JSON.parse(bidRequest?.data); + + const bids = (response?.bids || []).map(bid => ({ + requestId: bid?.bid_id || '1', // bid request id, the bid id + cpm: bid?.cpm || 0, + width: (bid?.size && bid?.size?.width) || 300, + height: (bid?.size && bid?.size?.height) || 250, + currency: bid?.currency || defaultCurrency, + ttl: bid?.ttl || defaultTtl, + creativeId: bid?.ssp ? `ssp:${bid.ssp}` : `${bid?.ad_id || 0}:${bid?.ig_id || 0}`, + netRevenue: bid?.net_revenue || true, + ad: bid?.ad || '', meta: { - advertiserDomains: response?.advertiserDomains || [], + advertiserDomains: bid?.advertiser_domains || [], }, - }] : null; - - log('interpretResponse', {serverResponse, bidRequest, bidRequestData, bids}); - - if (response.enable_pa === false) { return bids; } - - const fledgeAuctionConfig = { - seller: baseUrl, - decisionLogicUrl: `${baseUrl}/js/ssp.js`, - interestGroupBuyers: [baseUrl], - perBuyerSignals: {}, - auctionSignals: { - size: bidRequestData.sizes ? {width: bidRequestData?.sizes[0][0] || 300, height: bidRequestData?.sizes[0][1] || 250} : null, - }, - }; + })); - const fledgeAuctionConfigs = [{bidId: response.bid_id, config: fledgeAuctionConfig}]; + log('interpretRespoonetagnse', {serverResponse, bidRequest, bidRequestData, bids}); + + if (response?.enable_pa === false) { return bids; } + + const paapi = response.bids.map(bid => ({ + bidId: bid?.bid_id, + config: { + seller: baseUrl, + decisionLogicUrl: `${baseUrl}/js/ssp.js`, + interestGroupBuyers: [baseUrl], + requestedSize: bid?.size, + auctionSignals: { + size: bid?.size, + }, + perBuyerSignals: { + [baseUrl]: { + prebid_paapi: true, + prebid_bid_id: bid?.bid_id, + prebid_request_id: bidRequestData.request_id, + placement_id: bid.placement_id, + // floor, + is_sra: true, + endpoint_url: endpointUrl, + }, + } + } + })); - return {bids, fledgeAuctionConfigs}; + return {bids, paapi}; } -function report(type = 'impression', data = {}) { +function report(type, data) { // noinspection JSCheckFunctionSignatures - return fetch(`${endpointUrl}/report/${type}`, { + return fetch(`${endpointUrl}/go/report/${type}`, { body: JSON.stringify(data), method: 'POST', contentType: 'text/plain' @@ -128,20 +151,23 @@ function onBidWon(bid) { let data = { bid_id: bid?.bidId, - placement_id: bid?.params ? bid?.params[0]?.placementId : 0, + placement_id: bid.params ? (bid?.params[0]?.placementId || '0') : '0', spent: bid?.cpm, currency: bid?.currency, }; - if (bid.creativeId) { - if (bid.creativeId.toString().startsWith('ssp:')) { - data.ssp = bid.creativeId.split(':')[1]; + if (bid?.creativeId) { + const parts = bid.creativeId.toString().split(':'); + + if (parts[0] === 'ssp') { + data.ssp = parts[1]; } else { - data.ad_id = bid.creativeId; + data.ad_id = parts[0] + data.ig_id = parts[1] } } - return report(`impression`, data); + return report('impression', data); } function onTimeout(timeoutData) { diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md index 953c911cd2bb..1470eb2f9c44 100644 --- a/modules/luceadBidAdapter.md +++ b/modules/luceadBidAdapter.md @@ -10,20 +10,31 @@ Maintainer: prebid@lucead.com Module that connects to Lucead demand source to fetch bids. -# Test Parameters +# Params type definition + +```typescript +type Params = { + placementId: string; + region?: 'eu' | 'us' | 'ap'; +}; ``` -const adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'lucead', - params: { - placementId: '2', - } - } - ] - } - ]; + +# Test Parameters + +```javascript +const adUnits=[ + { + code:'test-div', + sizes:[[300,250]], + bids:[ + { + bidder:'lucead', + params:{ + placementId:'2', + region:'us', // optional: 'eu', 'us', 'ap' + } + } + ] + } +]; ``` diff --git a/test/spec/modules/luceadBidAdapter_spec.js b/test/spec/modules/luceadBidAdapter_spec.js index 72bc7cc2d6ee..6f61071b6535 100644 --- a/test/spec/modules/luceadBidAdapter_spec.js +++ b/test/spec/modules/luceadBidAdapter_spec.js @@ -28,6 +28,7 @@ describe('Lucead Adapter', () => { bidder: 'lucead', params: { placementId: '1', + region: 'eu', }, }; }); @@ -39,7 +40,10 @@ describe('Lucead Adapter', () => { describe('onBidWon', function () { let sandbox; - const bid = { foo: 'bar', creativeId: 'ssp:improve' }; + const bids = [ + { foo: 'bar', creativeId: 'ssp:improve' }, + { foo: 'bar', creativeId: '123:456' }, + ]; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -47,8 +51,11 @@ describe('Lucead Adapter', () => { it('should trigger impression pixel', function () { sandbox.spy(ajax, 'fetch'); - spec.onBidWon(bid); - expect(ajax.fetch.args[0][0]).to.match(/report\/impression$/); + + for (const bid of bids) { + spec.onBidWon(bid); + expect(ajax?.fetch?.args[0][0]).to.match(/report\/impression$/); + } }); afterEach(function () { @@ -77,49 +84,62 @@ describe('Lucead Adapter', () => { it('should have a post method', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].method).to.equal('POST'); + expect(request.method).to.equal('POST'); }); it('should contains a request id equals to the bid id', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(JSON.parse(request[0].data).bid_id).to.equal(bidRequests[0].bidId); + expect(JSON.parse(request.data).bid_requests[0].bid_id).to.equal(bidRequests[0].bidId); }); - it('should have an url that contains sub keyword', function () { + it('should have an url that contains sra keyword', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].url).to.match(/sub/); + expect(request.url).to.contain('/prebid/sra'); }); }); describe('interpretResponse', function () { - const serverResponse = { - body: { - 'bid_id': '2daf899fbe4c52', - 'request_id': '13aaa3df18bfe4', - 'ad': 'Ad', - 'ad_id': '3890677904', - 'cpm': 3.02, - 'currency': 'USD', - 'time': 1707257712095, - 'size': {'width': 300, 'height': 250}, - } + const serverResponseBody = { + 'request_id': '17548f887fb722', + 'bids': [ + { + 'bid_id': '2d663fdd390b49', + 'ad': '\u003chtml lang="en"\u003e\u003cbody style="margin:0;background-color:#FFF"\u003e\u003ciframe src="urn:uuid:fb81a0f9-b83a-4f27-8676-26760d090f1c" style="width:300px;height:250px;border:none" seamless \u003e\u003c/iframe\u003e\u003c/body\u003e\u003c/html\u003e', + 'size': { + 'width': 300, + 'height': 250 + }, + 'ad_id': '1', + 'ig_id': '1', + 'cpm': 1, + 'currency': 'EUR', + 'time': 0, + 'ssp': '', + 'placement_id': '1', + 'is_pa': true + } + ] }; - const bidRequest = {data: JSON.stringify({ - 'request_id': '13aaa3df18bfe4', - 'domain': '7cdb-2a02-8429-e4a0-1701-bc69-d51c-86e-b279.ngrok-free.app', - 'bid_id': '2daf899fbe4c52', - 'sizes': [[300, 250]], - 'media_types': {'banner': {'sizes': [[300, 250]]}}, - 'fledge_enabled': true, - 'enable_contextual': true, - 'enable_pa': true, - 'params': {'placementId': '1'}, - })}; + const serverResponse = {body: serverResponseBody}; + + const bidRequest = { + data: JSON.stringify({ + 'request_id': '17548f887fb722', + 'domain': 'lucead.com', + 'bid_requests': [{ + 'bid_id': '2d663fdd390b49', + 'sizes': [[300, 250], [300, 150]], + 'media_types': {'banner': {'sizes': [[300, 250], [300, 150]]}}, + 'placement_id': '1' + }], + }), + }; it('should get correct bid response', function () { const result = spec.interpretResponse(serverResponse, bidRequest); + // noinspection JSCheckFunctionSignatures expect(Object.keys(result.bids[0])).to.have.members([ 'requestId', 'cpm', @@ -135,7 +155,7 @@ describe('Lucead Adapter', () => { }); it('should return bid empty response', function () { - const serverResponse = {body: {cpm: 0}}; + const serverResponse = {body: {bids: [{cpm: 0}]}}; const bidRequest = {data: '{}'}; const result = spec.interpretResponse(serverResponse, bidRequest); expect(result.bids[0].ad).to.be.equal(''); @@ -154,18 +174,11 @@ describe('Lucead Adapter', () => { expect(Object.keys(result.bids[0].meta)).to.include.members(['advertiserDomains']); }); - it('should support disabled contextual bids', function () { - const serverResponseWithDisabledContectual = deepClone(serverResponse); - serverResponseWithDisabledContectual.body.enable_contextual = false; - const result = spec.interpretResponse(serverResponseWithDisabledContectual, bidRequest); - expect(result.bids).to.be.null; - }); - - it('should support disabled Protected Audience', function () { - const serverResponseWithEnablePaFalse = deepClone(serverResponse); - serverResponseWithEnablePaFalse.body.enable_pa = false; - const result = spec.interpretResponse(serverResponseWithEnablePaFalse, bidRequest); - expect(result.fledgeAuctionConfigs).to.be.undefined; + it('should support enable_pa = false', function () { + serverResponse.body.enable_pa = false; + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.be.an('array'); + expect(result[0].cpm).to.be.greaterThan(0); }); }); });