diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 039f27e5de0..ec4401b77c9 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,94 +1,93 @@ import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; -import { loadExternalScript } from '../src/adloader.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const ENDPOINT_URL = 'https://layer.serve.admatic.com.tr/pb'; -const SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +const SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.html'; const BIDDER_CODE = 'admatic'; - export const spec = { - code: 'admatic', - supportedMediaTypes: ['video', 'banner'], - /** - * 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. + code: BIDDER_CODE, + aliases: [ + {code: 'pixad'} + ], + supportedMediaTypes: [BANNER, VIDEO], + /** f + * @param {object} bid + * @return {boolean} */ - isBidRequestValid: function(bid) { + isBidRequestValid: (bid) => { let isValid = false; - if (typeof bid.params !== 'undefined') { - let isValidNetworkId = _validateId(getValue(bid.params, 'networkId')); - isValid = isValidNetworkId;// && isValidTypeId; + if (bid?.params) { + const isValidNetworkId = _validateId(getValue(bid.params, 'networkId')); + const isValidHost = _validateString(getValue(bid.params, 'host')); + isValid = isValidNetworkId && isValidHost; } if (!isValid) { - logError('AdMatic networkId parameters are required. Bid aborted.'); + logError(`${bid.bidder} networkId and host parameters are required. Bid aborted.`); } return isValid; }, + /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} an array of bids - * @return ServerRequest Info describing the request to the server. + * @param {BidRequest[]} validBidRequests + * @return {ServerRequest} */ - buildRequests: function(validBidRequests, bidderRequest) { + buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(buildRequestObject); const networkId = getValue(validBidRequests[0].params, 'networkId'); + const host = getValue(validBidRequests[0].params, 'host'); const currency = getValue(validBidRequests[0].params, 'currency') || 'TRY'; - - setTimeout(() => { - loadExternalScript(SYNC_URL, BIDDER_CODE); - }, bidderRequest.timeout); + const bidderName = validBidRequests[0].bidder; const payload = { - 'user': { - 'ua': navigator.userAgent + user: { + ua: navigator.userAgent }, - 'blacklist': [], - 'site': { - 'page': location.href, - 'ref': location.origin, - 'publisher': { - 'name': location.hostname, - 'publisherId': networkId + blacklist: [], + site: { + page: location.href, + ref: location.origin, + publisher: { + name: location.hostname, + publisherId: networkId } }, imp: bids, ext: { - 'cur': currency, - 'type': 'admatic' + cur: currency, + bidder: bidderName } }; - const payloadString = JSON.stringify(payload); - return { - method: 'POST', - url: ENDPOINT_URL, - data: payloadString, - options: { - contentType: 'application/json' - } - }; + if (payload) { + return { method: 'POST', url: `https://${host}/pb?bidder=${bidderName}`, data: payload, options: { contentType: 'application/json' } }; + } + }, + + getUserSyncs: function (syncOptions, responses) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: SYNC_URL + }]; + } }, + /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[]} */ interpretResponse: (response, request) => { const body = response.body || response; const bidResponses = []; - if (body.data.length > 0) { - body.data.forEach(function (bid) { + if (body && body?.data && isArray(body.data)) { + body.data.forEach(bid => { const resbid = { requestId: bid.id, cpm: bid.price, width: bid.width, height: bid.height, - currency: body.cur, + currency: body.cur || 'TRY', netRevenue: true, ad: bid.party_tag, creativeId: bid.creative_id, @@ -96,22 +95,68 @@ export const spec = { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, ttl: 360, - bidder: 'admatic', - timeToRespond: 1, - requestTimestamp: 1 + bidder: bid.bidder }; + bidResponses.push(resbid); }); - }; + } return bidResponses; } }; +function enrichSlotWithFloors(slot, bidRequest) { + try { + const slotFloors = {}; + + if (bidRequest.getFloor) { + if (bidRequest.mediaTypes?.banner) { + slotFloors.banner = {}; + const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) + bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = bidRequest.getFloor({ size: bannerSize, mediaType: BANNER })); + } + + if (bidRequest.mediaTypes?.video) { + slotFloors.video = {}; + const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); + } + + if (Object.keys(slotFloors).length > 0) { + if (!slot) { + slot = {} + } + Object.assign(slot, { + floors: slotFloors + }); + } + } + } catch (e) { + logError('Could not parse floors from Prebid: ' + e); + } +} + +function parseSizes(sizes, parser = s => s) { + if (sizes == undefined) { + return []; + } + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parser(size)); + } + return [parser(sizes)]; // or a single one ? (ie. [728,90]) +} + +function parseSize(size) { + return size[0] + 'x' + size[1]; +} + function buildRequestObject(bid) { const reqObj = {}; reqObj.size = getSizes(bid); reqObj.id = getBidIdParameter('bidId', bid); - reqObj.floor = getValue(bid.params, 'floor') || 0.01; + + enrichSlotWithFloors(reqObj, bid); + return reqObj; } @@ -144,4 +189,8 @@ function _validateId(id) { return (parseInt(id) > 0); } +function _validateString(str) { + return (typeof str == 'string'); +} + registerBidder(spec); diff --git a/modules/admaticBidAdapter.md b/modules/admaticBidAdapter.md index de84998d0af..2bf9afb3cdc 100644 --- a/modules/admaticBidAdapter.md +++ b/modules/admaticBidAdapter.md @@ -19,7 +19,7 @@ Use `admatic` as bidder. bidder: 'admatic', params: { networkId: 12345, - floor: 0.5 + host: 'layer.serve.admatic.com.tr' } }] },{ @@ -29,7 +29,7 @@ Use `admatic` as bidder. bidder: 'admatic', params: { networkId: 12345, - floor: 0.5 + host: 'layer.serve.admatic.com.tr' } }] }]; diff --git a/src/adloader.js b/src/adloader.js index 330b52b3ed5..64408683e9f 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -6,7 +6,6 @@ const _requestCache = new WeakMap(); const _approvedLoadExternalJSList = [ 'debugging', 'adloox', - 'admatic', 'criteo', 'outstream', 'adagio', diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index c7d391cfaca..65a7b4111b7 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec, storage} from 'modules/admaticBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {getStorageManager} from 'src/storageManager'; -const ENDPOINT = 'https://layer.serve.admatic.com.tr/v1'; +const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb?bidder=admatic'; describe('admaticBidAdapter', () => { const adapter = newBidder(spec); @@ -18,7 +18,8 @@ describe('admaticBidAdapter', () => { let bid = { 'bidder': 'admatic', 'params': { - 'networkId': 10433394 + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], @@ -37,10 +38,268 @@ describe('admaticBidAdapter', () => { delete bid.params; bid.params = { - 'networkId': 0 + 'networkId': 0, + 'host': 'layer.serve.admatic.com.tr' }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + let validRequest = [ { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + }, + 'user': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' + }, + 'blacklist': [], + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost', + 'publisherId': 12321312 + } + }, + 'imp': [ + { + 'size': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 728, + 'h': 90 + } + ], + 'id': '2205da7a81846b', + 'floors': { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + } + } + ], + 'ext': { + 'cur': 'USD', + 'bidder': 'admatic' + } + } ]; + let bidderRequest = { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + }, + 'user': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' + }, + 'blacklist': [], + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost', + 'publisherId': 12321312 + } + }, + 'imp': [ + { + 'size': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 728, + 'h': 90 + } + ], + 'id': '2205da7a81846b', + 'floors': { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + } + } + ], + 'ext': { + 'cur': 'USD', + 'bidder': 'admatic' + } + }; + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should properly build a banner request with floors', function () { + let bidRequests = [ + { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + } + }, + ]; + let bidderRequest = { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [728, 90]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + request.data.imp[0].floors = { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + }; + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid responses', function() { + let bids = { body: { + data: [ + { + 'id': 1, + 'creative_id': '374', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': '
' + } + ], + 'queryId': 'cdnbh24rlv0hhkpfpln0', + 'status': true + }}; + + let expectedResponse = [ + { + requestId: 1, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + netRevenue: true, + ad: '
', + creativeId: '374', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 360, + bidder: 'admatic' + } + ]; + const request = { + ext: { + 'cur': 'TRY', + 'type': 'admatic' + } + }; + let result = spec.interpretResponse(bids, {data: request}); + expect(result).to.eql(expectedResponse); + }); + + it('handles nobid responses', function () { + let request = { + ext: { + 'cur': 'TRY', + 'type': 'admatic' + } + }; + let bids = { body: { + data: [], + 'queryId': 'cdnbh24rlv0hhkpfpln0', + 'status': true + }}; + + let result = spec.interpretResponse(bids, {data: request}); + expect(result.length).to.equal(0); + }); + }); });