diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js new file mode 100644 index 00000000000..cd8b2462eb8 --- /dev/null +++ b/modules/big-richmediaBidAdapter.js @@ -0,0 +1,117 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {spec as baseAdapter} from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports + +const BIDDER_CODE = 'big-richmedia'; + +const metadataByRequestId = {}; + +export const spec = { + version: '1.4.0', + code: BIDDER_CODE, + gvlid: baseAdapter.GVLID, // use base adapter gvlid + supportedMediaTypes: [ BANNER, VIDEO ], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!baseAdapter.isBidRequestValid) { return true; } + return baseAdapter.isBidRequestValid(bid); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + if (!baseAdapter.buildRequests) { return []; } + + const publisherId = config.getConfig('bigRichmedia.publisherId'); + if (typeof publisherId !== 'string') { return []; } + + bidRequests.forEach(bidRequest => { + if (bidRequest.params.format === 'skin' && bidRequest.mediaTypes.banner) { + bidRequest.mediaTypes.banner.sizes.push([1800, 1000]); + } + metadataByRequestId[bidRequest.bidId] = { placementId: bidRequest.adUnitCode, bidder: bidRequest.bidder }; + }); + return baseAdapter.buildRequests(bidRequests, bidderRequest); + }, + + /** + * 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. + */ + interpretResponse: function (serverResponse, params) { + const publisherId = config.getConfig('bigRichmedia.publisherId'); + if (typeof publisherId !== 'string') { return []; } + + const bids = baseAdapter.interpretResponse(serverResponse, params); + bids.forEach(bid => { + const { placementId, bidder } = metadataByRequestId[bid.requestId] || {}; + const { width = 1, height = 1, ad, creativeId = '', cpm, vastXml, vastUrl } = bid; + const bidRequest = params.bidderRequest.bids.find(({ bidId }) => bidId === bid.requestId); + const format = (bidRequest && bidRequest.params && bidRequest.params.format) || 'video-sticky-footer'; + const isReplayable = bidRequest && bidRequest.params && bidRequest.params.isReplayable; + const customSelector = bidRequest && bidRequest.params && bidRequest.params.customSelector; + const renderParams = { + adm: ad, + vastXml, + vastUrl, + width, + height, + placementId, + bidId: bid.requestId, + creativeId: `${creativeId}`, + bidder, + cpm, + format, + customSelector, + isReplayable + }; + const encoded = window.btoa(JSON.stringify(renderParams)); + bid.ad = ` + `; + + if (bid.mediaType !== 'banner') { // in case this is a video + bid.mediaType = 'banner'; + delete bid.renderer; + delete bid.vastUrl; + delete bid.vastXml; + bid.width = 1; + bid.height = 1; + } + }); + return bids; + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (!baseAdapter.getUserSyncs) { return []; } + return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); + }, + + transformBidParams: function (params, isOpenRtb) { + if (!baseAdapter.transformBidParams) { return params; } + return baseAdapter.transformBidParams(params, isOpenRtb); + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (!baseAdapter.onBidWon) { return; } + baseAdapter.onBidWon(bid); + } +} + +registerBidder(spec); diff --git a/modules/big-richmediaBidAdapter.md b/modules/big-richmediaBidAdapter.md new file mode 100644 index 00000000000..26f77e527fb --- /dev/null +++ b/modules/big-richmediaBidAdapter.md @@ -0,0 +1,82 @@ +# Overview + +``` +Module Name: BI.Garage Rich Media +Module Type: Bidder Adapter +Maintainer: mediaconsortium-develop@bi.garage.co.jp +``` + +# Description + +Module which renders richmedia demand from a Xandr seat + +### Global configuration + +```javascript +pbjs.setConfig({ + debug: false, + // …, + bigRichmedia: { + publisherId: 'A7FN99NZ98F5ZD4G', // Required + }, +}); +``` + +# AdUnit Configuration +```javascript +var adUnits = [ + // Skin adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'big-richmedia', + params: { + placementId: 12345, + format: 'skin' // This will automatically add 1800x1000 size to banner mediaType + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'big-richmedia', + params: { + placementId: 12345, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + }, + format: 'video-sticky-footer', // or 'video-sticky-top' + isReplayable: true // Default to false - choose if the video should be replayable or not. + customSelector: '#nav-bar' // custom selector for navbar + } + } + ] + } +]; +``` diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js new file mode 100644 index 00000000000..1e97e1ac1d7 --- /dev/null +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -0,0 +1,310 @@ +import { expect } from 'chai'; +import { spec } from 'modules/big-richmediaBidAdapter.js'; +import { auctionManager } from 'src/auctionManager.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; + +describe('bigRichMediaAdapterTests', function () { + before(function () { + config.setConfig({ + bigRichmedia: { + publisherId: '123ABC' + } + }); + }); + + after(function () { + config.resetConfig(); + }); + + describe('bidRequestValidity', function () { + const bid = { + 'bidder': 'bigRichmedia', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('bidRequest with zoneId and deliveryUrl params', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('bidRequest with no params is not valid', function () { + const localBid = Object.assign({}, bid); + localBid.params = {}; + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + }); + + describe('bidRequest', function () { + let getAdUnitsStub; + const bidRequests = [ + { + 'bidder': 'bigRichmedia', + 'params': { + 'placementId': '10433394' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600], [1800, 1000]] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600], [1800, 1000]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should have skin size', function () { + const bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + format: 'skin' + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.exist; + expect(payload.tags[0].sizes).to.have.lengthOf(3); + }); + + it('should build video bid request', function() { + const bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream', + format: 'sticky-top' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + }); + + describe('interpretResponse', function () { + let bfStub; + + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + const response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'https://lax1-ib.adnxs.com/impression', + 'https://www.test.com/tracker' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + const expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'appnexus': { + 'buyerMemberId': 958 + }, + 'meta': { + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'bsid': '958' + }] + } + } + } + ]; + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles outstream video responses', function () { + const response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).not.to.have.property('vastXml'); + expect(result[0]).not.to.have.property('vastUrl'); + expect(result[0]).to.have.property('width', 1); + expect(result[0]).to.have.property('height', 1); + expect(result[0]).to.have.property('mediaType', 'banner'); + }); + }); + + describe('getUserSyncs', function() { + const syncOptions = { + syncEnabled: false + }; + + it('should not return sync', function() { + const serverResponse = [{ body: '' }]; + const result = spec.getUserSyncs(syncOptions, serverResponse); + expect(result).to.be.undefined; + }); + }); + + describe('transformBidParams', function() { + it('cast placementId to number', function() { + const adUnit = { + code: 'adunit-code', + params: { + placementId: '456' + } + }; + const bid = { + params: { + placementId: '456' + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + }; + + const params = spec.transformBidParams({ placementId: '456' }, true, adUnit, [{ bidderCode: 'bigRichmedia', auctionId: bid.auctionId, bids: [bid] }]); + + expect(params.placement_id).to.exist; + expect(params.placement_id).to.be.a('number'); + }); + }); + + describe('onBidWon', function() { + it('Should not have any error', function() { + const result = spec.onBidWon({}); + expect(true).to.be.true; + }); + }); +});