diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js new file mode 100644 index 00000000000..e79878e0b3a --- /dev/null +++ b/modules/integr8BidAdapter.js @@ -0,0 +1,141 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'integr8'; +const ENDPOINT_URL = 'https://integr8.central.gjirafa.tech/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + /** + * 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. + */ + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.propertyId && bid.params.placementId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let propertyId = ''; + let pageViewGuid = ''; + let storageId = ''; + let bidderRequestId = ''; + let url = ''; + let contents = []; + let data = {}; + + if (bidderRequest) { + bidderRequestId = bidderRequest.bidderRequestId; + + if (bidderRequest.refererInfo) { + url = bidderRequest.refererInfo.referer; + } + } + + let placements = validBidRequests.map(bidRequest => { + if (!propertyId) { propertyId = bidRequest.params.propertyId; } + if (!pageViewGuid) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } + if (!storageId) { storageId = bidRequest.params.storageId || ''; } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (!Object.keys(data).length && bidRequest.params.data && Object.keys(bidRequest.params.data).length) { data = bidRequest.params.data; } + + return { + sizes: generateSizeParam(bidRequest.sizes), + adUnitId: bidRequest.adUnitCode, + placementId: bidRequest.params.placementId, + bidid: bidRequest.bidId, + count: bidRequest.params.count, + skipTime: utils.deepAccess(bidRequest, 'mediaTypes.video.skipafter', bidRequest.params.skipTime), + floor: getBidFloor(bidRequest) + }; + }); + + let body = { + propertyId: propertyId, + pageViewGuid: pageViewGuid, + storageId: storageId, + url: url, + requestid: bidderRequestId, + placements: placements, + contents: contents, + data: data + } + + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: body + }]; + }, + /** + * 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. + */ + interpretResponse: function (serverResponse) { + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: responses[i].BidId, + cpm: responses[i].CPM, + width: responses[i].Width, + height: responses[i].Height, + creativeId: responses[i].CreativeId, + currency: responses[i].Currency, + netRevenue: responses[i].NetRevenue, + ttl: responses[i].TTL, + referrer: responses[i].Referrer, + ad: responses[i].Ad, + vastUrl: responses[i].VastUrl, + mediaType: responses[i].MediaType, + meta: { + advertiserDomains: Array.isArray(responses[i].ADomain) ? responses[i].ADomain : [] + } + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +export function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*' + }); + + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'EUR') { + return floor.floor; + } + + return null; +} + +registerBidder(spec); diff --git a/modules/integr8BidAdapter.md b/modules/integr8BidAdapter.md new file mode 100644 index 00000000000..eadab7acdb3 --- /dev/null +++ b/modules/integr8BidAdapter.md @@ -0,0 +1,67 @@ +# Overview +Module Name: Integr8 Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com + +# Description +Integr8 Bidder Adapter for Prebid.js. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'integr8', + params: { + propertyId: '105109', //Required + placementId: '846835', //Required + data: { //Optional + catalogs: [{ + catalogId: "699229", + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + }, + { + code: 'test-div', + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: 'integr8', + params: { + propertyId: '105109', //Required + placementId: '846830', //Required + data: { //Optional + catalogs: [{ + catalogId: "699229", + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } + } + } + }] + } +]; +``` diff --git a/test/spec/modules/integr8BidAdapter_spec.js b/test/spec/modules/integr8BidAdapter_spec.js new file mode 100644 index 00000000000..8c5a4b47903 --- /dev/null +++ b/test/spec/modules/integr8BidAdapter_spec.js @@ -0,0 +1,226 @@ +import { expect } from 'chai'; +import { spec, getBidFloor } from 'modules/integr8BidAdapter'; + +describe('integr8AdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId or placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'integr8', + params: {} + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'integr8', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': '699229', + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[728, 90]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://integr8.central.gjirafa.tech/bid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data).to.exist; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data.placements).to.exist; + expect(requestItem.data.placements.length).to.equal(1); + expect(requestItem.data.placements[0].sizes).to.equal('728x90'); + }); + }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://integr8.central.gjirafa.tech/bid', + 'data': { + 'sizes': '728x90', + 'adUnitId': 'hb-leaderboard', + 'placementId': '{placementId}', + 'propertyId': '{propertyId}', + 'pageViewGuid': '{pageViewGuid}', + 'url': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 728, + 'Height': 90, + 'Referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'Ad': '