From 217c8f66c5a5c08076a9f75e8b6be2d05a067fcf Mon Sep 17 00:00:00 2001 From: CPMStar Date: Wed, 11 Aug 2021 13:36:04 -0700 Subject: [PATCH] CPMStar Bid Adapter: Add adomain support for Prebid 5.x (#7284) * added cpmstarBidAdapter with meta.advertiserDomains support * fix linting Co-authored-by: Chris Huie --- modules/cpmstarBidAdapter.js | 183 ++++++++++++++++ test/spec/modules/cpmstarBidAdapter_spec.js | 231 ++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100755 modules/cpmstarBidAdapter.js create mode 100755 test/spec/modules/cpmstarBidAdapter_spec.js diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js new file mode 100755 index 00000000000..14c0d43add7 --- /dev/null +++ b/modules/cpmstarBidAdapter.js @@ -0,0 +1,183 @@ + +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'cpmstar'; + +const ENDPOINT_DEV = 'https://dev.server.cpmstar.com/view.aspx'; +const ENDPOINT_STAGING = 'https://staging.server.cpmstar.com/view.aspx'; +const ENDPOINT_PRODUCTION = 'https://server.cpmstar.com/view.aspx'; + +const DEFAULT_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; + +function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + pageID: Math.floor(Math.random() * 10e6), + + getMediaType: function (bidRequest) { + if (bidRequest == null) return BANNER; + return !utils.deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; + }, + + getPlayerSize: function (bidRequest) { + var playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (playerSize == null) return [640, 440]; + if (playerSize[0] != null) playerSize = playerSize[0]; + if (playerSize == null || playerSize[0] == null || playerSize[1] == null) return [640, 440]; + return playerSize; + }, + + isBidRequestValid: function (bid) { + return ((typeof bid.params.placementId === 'string') && !!bid.params.placementId.length) || (typeof bid.params.placementId === 'number'); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + var requests = []; + // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch. + + for (var i = 0; i < validBidRequests.length; i++) { + var bidRequest = validBidRequests[i]; + var referer = encodeURIComponent(bidderRequest.refererInfo.referer); + var e = utils.getBidIdParameter('endpoint', bidRequest.params); + var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; + var mediaType = spec.getMediaType(bidRequest); + var playerSize = spec.getPlayerSize(bidRequest); + var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); + var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + + '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + + '&requestid=' + bidRequest.bidId + + '&referer=' + encodeURIComponent(referer); + + if (bidRequest.schain && bidRequest.schain.nodes) { + var schain = bidRequest.schain; + var schainString = ''; + schainString += schain.ver + ',' + schain.complete; + for (var i2 = 0; i2 < schain.nodes.length; i2++) { + var node = schain.nodes[i2]; + schainString += '!' + + fixedEncodeURIComponent(node.asi || '') + ',' + + fixedEncodeURIComponent(node.sid || '') + ',' + + fixedEncodeURIComponent(node.hp || '') + ',' + + fixedEncodeURIComponent(node.rid || '') + ',' + + fixedEncodeURIComponent(node.name || '') + ',' + + fixedEncodeURIComponent(node.domain || ''); + } + url += '&schain=' + schainString + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString != null) { + url += '&gdpr_consent=' + bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies != null) { + url += '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + } + + if (bidderRequest.uspConsent != null) { + url += '&us_privacy=' + bidderRequest.uspConsent; + } + + if (config.getConfig('coppa')) { + url += '&tfcd=' + (config.getConfig('coppa') ? 1 : 0); + } + + requests.push({ + method: 'GET', + url: url, + bidRequest: bidRequest, + }); + } + + return requests; + }, + + interpretResponse: function (serverResponse, request) { + var bidRequest = request.bidRequest; + var mediaType = spec.getMediaType(bidRequest); + + var bidResponses = []; + + if (!Array.isArray(serverResponse.body)) { + serverResponse.body = [serverResponse.body]; + } + + for (var i = 0; i < serverResponse.body.length; i++) { + var raw = serverResponse.body[i]; + var rawBid = raw.creatives[0]; + if (!rawBid) { + utils.logWarn('cpmstarBidAdapter: server response failed check'); + return; + } + var cpm = (parseFloat(rawBid.cpm) || 0); + + if (!cpm) { + utils.logWarn('cpmstarBidAdapter: server response failed check. Missing cpm') + return; + } + + var bidResponse = { + requestId: rawBid.requestid, + cpm: cpm, + width: rawBid.width || 0, + height: rawBid.height || 0, + currency: rawBid.currency ? rawBid.currency : DEFAULT_CURRENCY, + netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, + ttl: rawBid.ttl ? rawBid.ttl : DEFAULT_TTL, + creativeId: rawBid.creativeid || 0, + meta: { + advertiserDomains: rawBid.adomain ? rawBid.adomain : [] + } + }; + + if (rawBid.hasOwnProperty('dealId')) { + bidResponse.dealId = rawBid.dealId + } + + if (mediaType == BANNER && rawBid.code) { + bidResponse.ad = rawBid.code + (rawBid.px_cr ? "\n" : ''); + } else if (mediaType == VIDEO && rawBid.creativemacros && rawBid.creativemacros.HTML5VID_VASTSTRING) { + var playerSize = spec.getPlayerSize(bidRequest); + if (playerSize != null) { + bidResponse.width = playerSize[0]; + bidResponse.height = playerSize[1]; + } + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = rawBid.creativemacros.HTML5VID_VASTSTRING; + } else { + return utils.logError('bad response', rawBid); + } + + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + if (serverResponses.length == 0 || !serverResponses[0].body) return syncs; + var usersyncs = serverResponses[0].body[0].syncs; + if (!usersyncs || usersyncs.length < 0) return syncs; + for (var i = 0; i < usersyncs.length; i++) { + var us = usersyncs[i]; + if ((us.type === 'image' && syncOptions.pixelEnabled) || (us.type == 'iframe' && syncOptions.iframeEnabled)) { + syncs.push(us); + } + } + return syncs; + } + +}; +registerBidder(spec); diff --git a/test/spec/modules/cpmstarBidAdapter_spec.js b/test/spec/modules/cpmstarBidAdapter_spec.js new file mode 100755 index 00000000000..285fca9690a --- /dev/null +++ b/test/spec/modules/cpmstarBidAdapter_spec.js @@ -0,0 +1,231 @@ +import { expect } from 'chai'; +import { spec } from 'modules/cpmstarBidAdapter.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; + +const valid_bid_requests = [{ + 'bidder': 'cpmstar', + 'params': { + 'placementId': '57' + }, + 'sizes': [[300, 250]], + 'bidId': 'bidId' +}]; + +const bidderRequest = { + refererInfo: { + referer: 'referer', + reachedTop: false, + } +}; + +const serverResponse = { + body: [{ + creatives: [{ + cpm: 1, + width: 0, + height: 0, + currency: 'USD', + netRevenue: true, + ttl: 1, + creativeid: '1234', + requestid: '11123', + code: 'no idea', + media: 'banner', + } + ], + syncs: [{ type: 'image', url: 'https://server.cpmstar.com/pixel.aspx' }] + }] +}; + +describe('Cpmstar Bid Adapter', function () { + describe('isBidRequestValid', function () { + it('should return true since the bid is valid', + function () { + var bid = { params: { placementId: 123456 } }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }) + + it('should return false since the bid is invalid', function () { + var bid = { params: { placementId: '' } }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }) + + it('should return a valid player size', function () { + var bid = { + mediaTypes: { + video: { + playerSize: [[960, 540]] + } + } + } + expect(spec.getPlayerSize(bid)[0]).to.equal(960); + expect(spec.getPlayerSize(bid)[1]).to.equal(540); + }) + + it('should return a default player size', function () { + var bid = { + mediaTypes: { + video: { + playerSize: null + } + } + } + expect(spec.getPlayerSize(bid)[0]).to.equal(640); + expect(spec.getPlayerSize(bid)[1]).to.equal(440); + }) + }); + + describe('buildRequests', function () { + it('should produce a valid production request', function () { + var requests = spec.buildRequests(valid_bid_requests, bidderRequest); + expect(requests[0]).to.have.property('method'); + expect(requests[0]).to.have.property('url'); + expect(requests[0]).to.have.property('bidRequest'); + expect(requests[0].url).to.include('https://server.cpmstar.com/view.aspx'); + }); + it('should produce a valid staging request', function () { + var stgReq = deepClone(valid_bid_requests); + stgReq[0].params.endpoint = 'staging'; + var requests = spec.buildRequests(stgReq, bidderRequest); + expect(requests[0]).to.have.property('method'); + expect(requests[0]).to.have.property('url'); + expect(requests[0]).to.have.property('bidRequest'); + expect(requests[0].url).to.include('https://staging.server.cpmstar.com/view.aspx'); + }); + it('should produce a valid dev request', function () { + var devReq = deepClone(valid_bid_requests); + devReq[0].params.endpoint = 'dev'; + var requests = spec.buildRequests(devReq, bidderRequest); + expect(requests[0]).to.have.property('method'); + expect(requests[0]).to.have.property('url'); + expect(requests[0]).to.have.property('bidRequest'); + expect(requests[0].url).to.include('https://dev.server.cpmstar.com/view.aspx'); + }); + it('should produce a request with support for GDPR', function () { + var gdpr_bidderRequest = deepClone(bidderRequest); + gdpr_bidderRequest.gdprConsent = { + consentString: 'consentString', + gdprApplies: true + }; + var requests = spec.buildRequests(valid_bid_requests, gdpr_bidderRequest); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('gdpr_consent=consentString'); + expect(requests[0].url).to.include('gdpr=1'); + }); + it('should produce a request with support for USP', function () { + var usp_bidderRequest = deepClone(bidderRequest); + usp_bidderRequest.uspConsent = '1YYY'; + var requests = spec.buildRequests(valid_bid_requests, usp_bidderRequest); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('us_privacy=1YYY'); + }); + it('should produce a request with support for COPPA', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + var requests = spec.buildRequests(valid_bid_requests, bidderRequest); + config.getConfig.restore(); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('tfcd=1'); + }); + }); + + it('should produce a request with support for OpenRTB SupplyChain', function () { + var reqs = deepClone(valid_bid_requests); + reqs[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1 + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1 + } + ] + }; + var requests = spec.buildRequests(reqs, bidderRequest); + expect(requests[0]).to.have.property('url'); + expect(requests[0].url).to.include('&schain=1.0,1!exchange1.com,1234,1,,,!exchange2.com,abcd,1,,,'); + }); + + describe('interpretResponse', function () { + const request = { + bidRequest: { + mediaType: 'BANNER' + } + }; + + it('should return a valid bidresponse array', function () { + var r = spec.interpretResponse(serverResponse, request) + var c = serverResponse.body[0].creatives[0]; + expect(r[0].length).to.not.equal(0); + expect(r[0].requestId).equal(c.requestid); + expect(r[0].creativeId).equal(c.creativeid); + expect(r[0].cpm).equal(c.cpm); + expect(r[0].width).equal(c.width); + expect(r[0].height).equal(c.height); + expect(r[0].currency).equal(c.currency); + expect(r[0].netRevenue).equal(c.netRevenue); + expect(r[0].ttl).equal(c.ttl); + expect(r[0].ad).equal(c.code); + }); + + it('should return a valid bidresponse array from a non-array-body', function () { + var r = spec.interpretResponse({ body: serverResponse.body[0] }, request) + var c = serverResponse.body[0].creatives[0]; + expect(r[0].length).to.not.equal(0); + expect(r[0].requestId).equal(c.requestid); + expect(r[0].creativeId).equal(c.creativeid); + expect(r[0].cpm).equal(c.cpm); + expect(r[0].width).equal(c.width); + expect(r[0].height).equal(c.height); + expect(r[0].currency).equal(c.currency); + expect(r[0].netRevenue).equal(c.netRevenue); + expect(r[0].ttl).equal(c.ttl); + expect(r[0].ad).equal(c.code); + }); + + it('should return undefined due to an invalid cpm value', function () { + var badServer = deepClone(serverResponse); + badServer.body[0].creatives[0].cpm = 0; + var c = spec.interpretResponse(badServer, request); + expect(c).to.be.undefined; + }); + + it('should return undefined due to a bad response', function () { + var badServer = deepClone(serverResponse); + badServer.body[0].creatives[0].code = null; + var c = spec.interpretResponse(badServer, request); + expect(c).to.be.undefined; + }); + + it('should return a valid response with a dealId', function () { + var dealServer = deepClone(serverResponse); + dealServer.body[0].creatives[0].dealId = 'deal'; + expect(spec.interpretResponse(dealServer, request)[0].dealId).to.equal('deal'); + }); + }); + + describe('getUserSyncs', function () { + var sres = [deepClone(serverResponse)]; + + it('should return a valid pixel sync', function () { + var syncs = spec.getUserSyncs({ pixelEnabled: true }, sres); + expect(syncs.length).equal(1); + expect(syncs[0].type).equal('image'); + expect(syncs[0].url).equal('https://server.cpmstar.com/pixel.aspx'); + }); + + it('should return a valid iframe sync', function () { + sres[0].body[0].syncs[0].type = 'iframe'; + var syncs = spec.getUserSyncs({ iframeEnabled: true }, sres); + expect(syncs.length).equal(1); + expect(syncs[0].type).equal('iframe'); + expect(syncs[0].url).equal('https://server.cpmstar.com/pixel.aspx'); + }); + }); +});