diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 19fbe827eb1..f44fde0554d 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -383,7 +383,7 @@ function storeConsentData(cmpConsentObject) { vendorData: (cmpConsentObject) || undefined, gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope }; - if (cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { + if (cmpConsentObject && cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { consentData.addtlConsent = cmpConsentObject.addtlConsent; }; } diff --git a/modules/decenteradsBidAdapter.js b/modules/decenteradsBidAdapter.js new file mode 100644 index 00000000000..823a59a3768 --- /dev/null +++ b/modules/decenteradsBidAdapter.js @@ -0,0 +1,90 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' +import * as utils from '../src/utils.js' + +const BIDDER_CODE = 'decenterads' +const URL = 'https://supply.decenterads.com/?c=o&m=multi' +const URL_SYNC = 'https://supply.decenterads.com/?c=o&m=cookie' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: function (opts) { + return Boolean(opts.bidId && opts.params && !isNaN(opts.params.placementId)) + }, + + buildRequests: function (validBidRequests) { + validBidRequests = validBidRequests || [] + let winTop = window + try { + window.top.location.toString() + winTop = window.top + } catch (e) { utils.logMessage(e) } + + const location = utils.getWindowLocation() + const placements = [] + + for (let i = 0; i < validBidRequests.length; i++) { + const p = validBidRequests[i] + + placements.push({ + placementId: p.params.placementId, + bidId: p.bidId, + traffic: p.params.traffic || BANNER + }) + } + + return { + method: 'POST', + url: URL, + data: { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language : '', + secure: +(location.protocol === 'https:'), + host: location.hostname, + page: location.pathname, + placements: placements + } + } + }, + + interpretResponse: function (opts) { + const body = opts.body + const response = [] + + for (let i = 0; i < body.length; i++) { + const item = body[i] + if (isBidResponseValid(item)) { + delete item.mediaType + response.push(item) + } + } + + return response + }, + + getUserSyncs: function (syncOptions, serverResponses) { + return [{ type: 'image', url: URL_SYNC }] + } +} + +registerBidder(spec) + +function isBidResponseValid (bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false + } + switch (bid['mediaType']) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad) + case VIDEO: + return Boolean(bid.vastUrl) + case NATIVE: + return Boolean(bid.title && bid.image && bid.impressionTrackers) + default: + return false + } +} diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js new file mode 100644 index 00000000000..ca7fb4af32d --- /dev/null +++ b/modules/gjirafaBidAdapter.js @@ -0,0 +1,91 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'gjirafa'; +const ENDPOINT_URL = 'https://central.gjirafa.com/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +export const spec = { + code: BIDDER_CODE, + /** + * 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.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 response = validBidRequests.map(bidRequest => { + let sizes = generateSizeParam(bidRequest.sizes); + let propertyId = bidRequest.params.propertyId; + let placementId = bidRequest.params.placementId; + let adUnitId = bidRequest.adUnitCode; + let pageViewGuid = bidRequest.params.pageViewGuid || ''; + let contents = bidRequest.params.contents || []; + const body = { + sizes: sizes, + adUnitId: adUnitId, + placementId: placementId, + propertyId: propertyId, + pageViewGuid: pageViewGuid, + url: bidderRequest ? bidderRequest.refererInfo.referer : '', + requestid: bidRequest.bidderRequestId, + bidid: bidRequest.bidId, + contents: contents + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: body + }; + }); + return response + }, + /** + * 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, bidRequest) { + window.adnResponse = serverResponse; + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: bidRequest.data.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 + }; + 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); +} + +registerBidder(spec); diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md index 1ec8222d8de..53d3a76c5ed 100644 --- a/modules/gjirafaBidAdapter.md +++ b/modules/gjirafaBidAdapter.md @@ -1,36 +1,51 @@ # Overview Module Name: Gjirafa Bidder Adapter Module Type: Bidder Adapter -Maintainer: agonq@gjirafa.com +Maintainer: drilon@gjirafa.com # Description Gjirafa Bidder Adapter for Prebid.js. # Test Parameters var adUnits = [ -{ - code: 'test-div', - sizes: [[728, 90]], // leaderboard - bids: [ - { - bidder: 'gjirafa', - params: { - placementId: '71-3' - } - } - ] -},{ - code: 'test-div', - sizes: [[300, 250]], // mobile rectangle - bids: [ - { - bidder: 'gjirafa', - params: { - minCPM: 0.0001, - minCPC: 0.001, - explicit: true - } - } - ] -} -]; \ No newline at end of file + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [ + { + bidder: 'gjirafa', + params: { + propertyId: '105227', + placementId: '846841' + } + } + ] + }, + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'gjirafa', + params: { + propertyId: '105227', + placementId: '846848', + contents: [ //optional + { + type: 'article', + id: '123' + } + ] + } + } + ] + } +]; diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js new file mode 100644 index 00000000000..4cdb5d45328 --- /dev/null +++ b/modules/malltvBidAdapter.js @@ -0,0 +1,91 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'malltv'; +const ENDPOINT_URL = 'https://central.mall.tv/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +export const spec = { + code: BIDDER_CODE, + /** + * 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.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 response = validBidRequests.map(bidRequest => { + let sizes = generateSizeParam(bidRequest.sizes); + let propertyId = bidRequest.params.propertyId; + let placementId = bidRequest.params.placementId; + let adUnitId = bidRequest.adUnitCode; + let pageViewGuid = bidRequest.params.pageViewGuid || ''; + let contents = bidRequest.params.contents || []; + const body = { + sizes: sizes, + adUnitId: adUnitId, + placementId: placementId, + propertyId: propertyId, + pageViewGuid: pageViewGuid, + url: bidderRequest ? bidderRequest.refererInfo.referer : '', + requestid: bidRequest.bidderRequestId, + bidid: bidRequest.bidId, + contents: contents + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: body + }; + }); + return response + }, + /** + * 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, bidRequest) { + window.adnResponse = serverResponse; + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: bidRequest.data.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 + }; + 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); +} + +registerBidder(spec); diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md new file mode 100644 index 00000000000..72db0cef6c7 --- /dev/null +++ b/modules/malltvBidAdapter.md @@ -0,0 +1,51 @@ +# Overview +Module Name: MallTV Bidder Adapter Module +Type: Bidder Adapter +Maintainer: drilon@gjirafa.com + +# Description +MallTV Bidder Adapter for Prebid.js. + +# Test Parameters +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, + bids: [ + { + bidder: 'malltv', + params: { + propertyId: '105134', + placementId: '846832' + } + } + ] + }, + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, + bids: [ + { + bidder: 'malltv', + params: { + propertyId: '105134', + placementId: '846832', + contents: [ //optional + { + type: 'video', + id: '123' + } + ] + } + } + ] + } +]; diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b3d559d956f..a7a3199675d 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -551,13 +551,15 @@ const OPEN_RTB_PROTOCOL = { } /** - * GAM Ad Unit - * @type {(string|undefined)} + * Copy GAM AdUnit and Name to imp */ - const gamAdUnit = utils.deepAccess(adUnit, 'fpd.context.adServer.adSlot'); - if (typeof gamAdUnit === 'string' && gamAdUnit) { - utils.deepSetValue(imp, 'ext.context.data.adslot', gamAdUnit); - } + ['name', 'adSlot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value); + } + }); Object.assign(imp, mediaTypes); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index da23cca62d1..979cc430f15 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -308,13 +308,15 @@ export const spec = { } /** - * GAM Ad Unit - * @type {(string|undefined)} + * Copy GAM AdUnit and Name to imp */ - const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); - if (typeof gamAdUnit === 'string' && gamAdUnit) { - utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', gamAdUnit); - } + ['name', 'adSlot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(bidRequest, `fpd.context.adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(data.imp[0].ext, `context.data.adserver.${name.toLowerCase()}`, value); + } + }); // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { diff --git a/test/spec/modules/decenteradsBidAdapter_spec.js b/test/spec/modules/decenteradsBidAdapter_spec.js new file mode 100644 index 00000000000..257094cae3a --- /dev/null +++ b/test/spec/modules/decenteradsBidAdapter_spec.js @@ -0,0 +1,207 @@ +import { expect } from 'chai' +import { spec } from '../../../modules/decenteradsBidAdapter.js' +import { deepStrictEqual, notEqual, ok, strictEqual } from 'assert' + +describe('DecenteradsAdapter', () => { + const bid = { + bidId: '9ec5b177515ee2e5', + bidder: 'decenterads', + params: { + placementId: 0, + traffic: 'banner' + } + } + + describe('isBidRequestValid', () => { + it('Should return true if there are bidId, params and placementId parameters present', () => { + strictEqual(true, spec.isBidRequestValid(bid)) + }) + + it('Should return false if at least one of parameters is not present', () => { + const b = { ...bid } + delete b.params.placementId + strictEqual(false, spec.isBidRequestValid(b)) + }) + }) + + describe('buildRequests', () => { + const serverRequest = spec.buildRequests([bid]) + + it('Creates a ServerRequest object with method, URL and data', () => { + ok(serverRequest) + ok(serverRequest.method) + ok(serverRequest.url) + ok(serverRequest.data) + }) + + it('Returns POST method', () => { + strictEqual('POST', serverRequest.method) + }) + + it('Returns valid URL', () => { + strictEqual('https://supply.decenterads.com/?c=o&m=multi', serverRequest.url) + }) + + it('Returns valid data if array of bids is valid', () => { + const { data } = serverRequest + strictEqual('object', typeof data) + deepStrictEqual(['deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'], Object.keys(data)) + strictEqual('number', typeof data.deviceWidth) + strictEqual('number', typeof data.deviceHeight) + strictEqual('string', typeof data.language) + strictEqual('string', typeof data.host) + strictEqual('string', typeof data.page) + notEqual(-1, [0, 1].indexOf(data.secure)) + + const placement = data.placements[0] + deepStrictEqual(['placementId', 'bidId', 'traffic'], Object.keys(placement)) + strictEqual(0, placement.placementId) + strictEqual('9ec5b177515ee2e5', placement.bidId) + strictEqual('banner', placement.traffic) + }) + + it('Returns empty data if no valid requests are passed', () => { + const { placements } = spec.buildRequests([]).data + + expect(spec.buildRequests([]).data.placements).to.be.an('array') + strictEqual(0, placements.length) + }) + }) + + describe('interpretResponse', () => { + const validData = [ + { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + vastUrl: 'decenterads.com', + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'decenterads.com', + title: 'Test', + image: 'decenterads.com', + creativeId: '2', + impressionTrackers: ['decenterads.com'], + ttl: 120, + cpm: 0.4, + requestId: '9ec5b177515ee2e5', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of validData) { + const { mediaType } = obj.body[0] + + it(`Should interpret ${mediaType} response`, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(1, response.length) + + const copy = { ...obj.body[0] } + delete copy.mediaType + deepStrictEqual(copy, response[0]) + }) + } + + const invalidData = [ + { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'decenterads.com', + title: 'Test', + impressionTrackers: ['decenterads.com'], + ttl: 120, + requestId: '9ec5b177515ee2e5', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of invalidData) { + const { mediaType } = obj.body[0] + + it(`Should return an empty array if invalid ${mediaType} response is passed `, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + } + + it('Should return an empty array if invalid response is passed', () => { + const response = spec.interpretResponse({ + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + }) + + describe('getUserSyncs', () => { + it('Returns valid URL and type', () => { + const expectedResult = [{ type: 'image', url: 'https://supply.decenterads.com/?c=o&m=cookie' }] + deepStrictEqual(expectedResult, spec.getUserSyncs()) + }) + }) +}) diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js new file mode 100644 index 00000000000..566b1243f62 --- /dev/null +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -0,0 +1,140 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gjirafaBidAdapter'; + +describe('gjirafaAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId orplacementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'gjirafa', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}' + }, + '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://central.gjirafa.com/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); + expect(requests[0].data.sizes).to.equal('728x90'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://central.gjirafa.com/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': '