diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 2c100bce27b..2744e38e820 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,32 +1,30 @@ -import { deepAccess, isFn, logError, getValue, getBidIdParameter, _each, isArray, triggerPixel } from '../src/utils.js'; +import { + deepAccess, + isFn, + logError, + getValue, + getBidIdParameter, + _each, + isArray, + triggerPixel, + formatQS, +} from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'audiencerun'; const BASE_URL = 'https://d.audiencerun.com'; const AUCTION_URL = `${BASE_URL}/prebid`; const TIMEOUT_EVENT_URL = `${BASE_URL}/ps/pbtimeout`; +const ERROR_EVENT_URL = `${BASE_URL}/js_log`; const DEFAULT_CURRENCY = 'USD'; let requestedBids = []; /** - * Gets bidder request referer - * - * @param {Object} bidderRequest - * @return {string} - */ -function getPageUrl(bidderRequest) { - return ( - config.getConfig('pageUrl') || - deepAccess(bidderRequest, 'refererInfo.referer') || - null - ); -} - -/** - * Returns bidfloor through floors module if available + * Returns bidfloor through floors module if available. * * @param {Object} bid * @returns {number} @@ -44,19 +42,53 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (_) { - return 0 + return 0; } } +/** + * Returns the most top page referer. + * + * @returns {string} + */ +function getPageReferer() { + let t, e; + do { + t = t ? t.parent : window; + try { + e = t.document.referrer; + } catch (_) { + break; + } + } while (t !== window.top); + return e; +} + +/** + * Returns bidder request page url. + * + * @param {Object} bidderRequest + * @return {string} + */ +function getPageUrl(bidderRequest) { + return ( + config.getConfig('pageUrl') || + deepAccess(bidderRequest, 'refererInfo.referer') || + getPageReferer() || + null + ); +} + export const spec = { - version: '1.1.0', + version: '1.2.0', code: BIDDER_CODE, + gvlid: 944, supportedMediaTypes: [BANNER], /** * Determines whether or not the given bid request is valid. * - * @param {object} bid The bid to validate. + * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { @@ -95,12 +127,19 @@ export const spec = { const payload = { libVersion: this.version, - referer: getPageUrl(bidderRequest), + pageUrl: config.getConfig('pageUrl'), + pageReferer: getPageReferer(), + referer: deepAccess(bidderRequest, 'refererInfo.referer'), + refererInfo: deepAccess(bidderRequest, 'refererInfo'), currencyCode: config.getConfig('currency.adServerCurrency'), timeout: config.getConfig('bidderTimeout'), bids, }; + payload.uspConsent = deepAccess(bidderRequest, 'uspConsent'); + payload.schain = deepAccess(bidRequests, '0.schain'); + payload.userId = deepAccess(bidRequests, '0.userId') ? createEidsArray(bidRequests[0].userId) : []; + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { consent: bidderRequest.gdprConsent.consentString, @@ -117,7 +156,7 @@ export const spec = { return { method: 'POST', - url: AUCTION_URL, + url: deepAccess(bidRequests, '0.params.auctionUrl', AUCTION_URL), data: JSON.stringify(payload), options: { withCredentials: true, @@ -201,7 +240,9 @@ export const spec = { } timeoutData.forEach((bid) => { - const bidOnTimeout = requestedBids.find((requestedBid) => requestedBid.bidId === bid.bidId); + const bidOnTimeout = requestedBids.find( + (requestedBid) => requestedBid.bidId === bid.bidId + ); if (bidOnTimeout) { triggerPixel( @@ -210,6 +251,18 @@ export const spec = { } }); }, + + /** + * Registers bidder specific code, which will execute if the bidder responded with an error. + * @param {{bidderRequest: object}} args An object from which we extract bidderRequest object. + */ + onBidderError: function ({ bidderRequest }) { + const queryString = formatQS({ + message: `Prebid.js: Server call for ${bidderRequest.bidderCode} failed.`, + url: encodeURIComponent(getPageUrl(bidderRequest)), + }); + triggerPixel(`${ERROR_EVENT_URL}/?${queryString}`); + }, }; registerBidder(spec); diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 7c1279f5073..7f1e059fa2a 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -8,65 +8,68 @@ const BID_SERVER_RESPONSE = { body: { bid: [ { - 'bidId': '51ef8751f9aead', - 'zoneId': '12345abcde', - 'crid': '5678', - 'cpm': 8.021951999999999999, - 'currency': 'USD', - 'w': 728, - 'h': 90, - 'isNet': false, - 'buying_type': 'rtb', - 'syncUrl': 'https://ac.audiencerun.com/f/sync.html', - 'adm': '', - 'adomain': ['example.com'] - } - ] - } + bidId: '51ef8751f9aead', + zoneId: '12345abcde', + crid: '5678', + cpm: 8.0219519, + currency: 'USD', + w: 728, + h: 90, + isNet: false, + buying_type: 'rtb', + syncUrl: 'https://ac.audiencerun.com/f/sync.html', + adm: '', + adomain: ['example.com'], + }, + ], + }, }; -describe('AudienceRun bid adapter tests', function() { +describe('AudienceRun bid adapter tests', function () { const adapter = newBidder(spec); - describe('inherited functions', function() { - it('exists and is a function', function() { + describe('inherited functions', function () { + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function() { + describe('isBidRequestValid', function () { let bid = { - 'bidder': 'audiencerun', - 'params': { - 'zoneId': '12345abcde' + bidder: 'audiencerun', + params: { + zoneId: '12345abcde', }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, }, - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee' + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + creativeId: 'er2ee', }; - it('should return true when required params found', function() { + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when zoneId is valid', function() { + it('should return true when zoneId is valid', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { - 'zoneId': '12345abcde' + zoneId: '12345abcde', }; expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required params are not passed', function() { + it('should return false when required params are not passed', function () { let bid = Object.assign({}, bid); delete bid.params; @@ -76,38 +79,45 @@ describe('AudienceRun bid adapter tests', function() { }); }); - describe('buildRequests', function() { + describe('buildRequests', function () { const bidRequests = [ { - 'bidder': 'audiencerun', - 'bidId': '51ef8751f9aead', - 'params': { - 'zoneId': '12345abcde' + bidder: 'audiencerun', + bidId: '51ef8751f9aead', + params: { + zoneId: '12345abcde', }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'mediaTypes': { - 'banner': { - 'sizes': [[320, 50], [300, 250], [300, 600]] - } + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + mediaTypes: { + banner: { + sizes: [ + [320, 50], + [300, 250], + [300, 600], + ], + }, }, - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1 - } + bidderRequestId: '418b37f85e772c', + auctionId: '18fd8b8b0bd757', + bidRequestsCount: 1, + }, ]; const bidRequest = bidRequests[0]; - it('sends a valid bid request to ENDPOINT via POST', function() { + it('sends a valid bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, { 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 + 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: { - canonicalUrl: 'https://example.com/canonical', - referer: 'https://example.com' - } + canonicalUrl: undefined, + referer: 'https://example.com', + numIframes: 0, + reachedTop: true, + }, }); expect(request.url).to.equal(ENDPOINT); @@ -116,7 +126,9 @@ describe('AudienceRun bid adapter tests', function() { const payload = JSON.parse(request.data); expect(payload.gdpr).to.exist; - expect(payload.bids).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(payload.bids) + .to.exist.and.to.be.an('array') + .and.to.have.lengthOf(1); expect(payload.referer).to.exist; const bid = payload.bids[0]; @@ -128,13 +140,13 @@ describe('AudienceRun bid adapter tests', function() { expect(bid.sizes[0].h).to.be.a('number'); }); - it('should send GDPR to endpoint and honor gdprApplies value', function() { + it('should send GDPR to endpoint and honor gdprApplies value', function () { let consentString = 'bogusConsent'; let bidderRequest = { - 'gdprConsent': { - 'consentString': consentString, - 'gdprApplies': true - } + gdprConsent: { + consentString: consentString, + gdprApplies: true, + }, }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -144,10 +156,10 @@ describe('AudienceRun bid adapter tests', function() { expect(payload.gdpr.applies).to.equal(true); let bidderRequest2 = { - 'gdprConsent': { - 'consentString': consentString, - 'gdprApplies': false - } + gdprConsent: { + consentString: consentString, + gdprApplies: false, + }, }; const request2 = spec.buildRequests(bidRequests, bidderRequest2); @@ -158,19 +170,32 @@ describe('AudienceRun bid adapter tests', function() { expect(payload2.gdpr.applies).to.equal(false); }); - it('should use a bidfloor with a 0 value', function() { + it('should use the auctionUrl passed from bid params', function () { + const bid = Object.assign({}, bidRequest, { + params: { + zoneId: '12345abcde', + auctionUrl: 'https://auction.url.audiencerun.com', + }, + }); + const request = spec.buildRequests([bid]); + + expect(request.url).to.exist; + expect(request.url).to.equal('https://auction.url.audiencerun.com'); + }); + + it('should use a bidfloor with a 0 value', function () { const bid = Object.assign({}, bidRequest); const request = spec.buildRequests([bid]); const payload = JSON.parse(request.data); expect(payload.bids[0].bidfloor).to.exist.and.to.equal(0); - }) + }); it('should use bidfloor param value', function () { const bid = Object.assign({}, bidRequest, { params: { - 'bidfloor': 0.2 - } - }) + bidfloor: 0.2, + }, + }); const request = spec.buildRequests([bid]); const payload = JSON.parse(request.data); expect(payload.bids[0].bidfloor).to.exist.and.to.equal(0.2); @@ -179,43 +204,104 @@ describe('AudienceRun bid adapter tests', function() { it('should use floors module value', function () { const bid = Object.assign({}, bidRequest, { params: { - 'bidfloor': 0.5 - } - }) + bidfloor: 0.5, + }, + }); bid.getFloor = () => { - return { floor: 1, currency: 'USD' } - } + return { floor: 1, currency: 'USD' }; + }; const request = spec.buildRequests([bid]); const payload = JSON.parse(request.data); expect(payload.bids[0].bidfloor).to.exist.and.to.equal(1); }); + + it('should add userid eids information to the request', function () { + const bid = Object.assign({}, bidRequest); + bid.userId = { + pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', + unsuported: '666', + } + + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + + expect(payload.userId).to.exist; + expect(payload.userId).to.deep.equal([ + { + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: '01EAJWWNEPN3CYMM5N8M5VXY22', + }, + ], + }, + ]); + }); + + it('should add schain object if available', function() { + const bid = Object.assign({}, bidRequest) + bid.schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + }, + ], + }; + + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + + expect(payload.schain).to.exist; + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + }, + ], + }); + }) }); describe('interpretResponse', function () { - const expectedResponse = [{ - 'requestId': '51ef8751f9aead', - 'cpm': 8.021951999999999999, - 'width': '728', - 'height': '90', - 'creativeId': '5678', - 'currency': 'USD', - 'netRevenue': false, - 'ttl': 300, - 'ad': '', - 'mediaType': 'banner', - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; + const expectedResponse = [ + { + requestId: '51ef8751f9aead', + cpm: 8.0219519, + width: '728', + height: '90', + creativeId: '5678', + currency: 'USD', + netRevenue: false, + ttl: 300, + ad: '', + mediaType: 'banner', + meta: { + advertiserDomains: ['example.com'], + }, + }, + ]; it('should get the correct bid response by display ad', function () { let result = spec.interpretResponse(BID_SERVER_RESPONSE); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.have.members( + Object.keys(expectedResponse[0]) + ); }); it('should handle empty bid response', function () { const response = { - body: {} + body: {}, }; let result = spec.interpretResponse(response); expect(result.length).to.equal(0); @@ -223,17 +309,19 @@ describe('AudienceRun bid adapter tests', function() { }); describe('getUserSyncs', function () { - const serverResponses = [ BID_SERVER_RESPONSE ]; + const serverResponses = [BID_SERVER_RESPONSE]; const syncOptions = { iframeEnabled: true }; - it('should return empty if no server responses', function() { + it('should return empty if no server responses', function () { const syncs = spec.getUserSyncs(syncOptions, []); - expect(syncs).to.deep.equal([]) + expect(syncs).to.deep.equal([]); }); it('should return user syncs', function () { const syncs = spec.getUserSyncs(syncOptions, serverResponses); - expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://ac.audiencerun.com/f/sync.html'}]) + expect(syncs).to.deep.equal([ + { type: 'iframe', url: 'https://ac.audiencerun.com/f/sync.html' }, + ]); }); });