From 37935fee4277eb0e1e72b3888d7ea12891d34c3a Mon Sep 17 00:00:00 2001 From: Ricardo Vega Date: Wed, 13 Dec 2017 14:46:06 -0600 Subject: [PATCH 1/4] GumGum Adapter for Prebid.js 1.0 --- modules/gumgumBidAdapter.js | 182 +++++++++++++++++++++ modules/gumgumBidAdapter.md | 40 +++++ test/spec/modules/gumgumBidAdapter_spec.js | 139 ++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 modules/gumgumBidAdapter.js create mode 100644 modules/gumgumBidAdapter.md create mode 100644 test/spec/modules/gumgumBidAdapter_spec.js diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js new file mode 100644 index 00000000000..400023e4630 --- /dev/null +++ b/modules/gumgumBidAdapter.js @@ -0,0 +1,182 @@ +import * as utils from 'src/utils' + +import { config } from 'src/config' +import { registerBidder } from 'src/adapters/bidderFactory' + +const BIDDER_CODE = 'gumgum' +const ALIAS_BIDDER_CODE = ['gg'] +const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` +const DT_CREDENTIALS = { member: 'YcXr87z2lpbB' } +const TIME_TO_LIVE = 60 +let browserParams = {}; + +// TODO: potential 0 values for browserParams sent to ad server +function _getBrowserParams() { + let topWindow + let topScreen + if (browserParams.vw) { + // we've already initialized browserParams, just return it. + return browserParams + } + + try { + topWindow = global.top; + topScreen = topWindow.screen; + } catch (error) { + utils.logError(error); + return browserParams + } + + browserParams = { + vw: topWindow.innerWidth, + vh: topWindow.innerHeight, + sw: topScreen.width, + sh: topScreen.height, + pu: utils.getTopWindowUrl(), + ce: utils.cookiesAreEnabled(), + dpr: topWindow.devicePixelRatio || 1 + } + return browserParams +} + +function getWrapperCode(wrapper, data) { + return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) +} + +// TODO: use getConfig() +function _getDigiTrustQueryParams() { + function getDigiTrustId () { + var digiTrustUser = (window.DigiTrust && window.DigiTrust.getUser) ? window.DigiTrust.getUser(DT_CREDENTIALS) : {}; + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || ''; + }; + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return {}; + } + return { + 'dt': digiTrustId.id + }; +} + +/** + * 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. + */ +function isBidRequestValid (bid) { + const { + params, + adUnitCode + } = bid; + + switch (true) { + case !!(params.inScreen): break; + case !!(params.inSlot): break; + default: + utils.logWarn(`[GumGum] No product selected for the placement ${adUnitCode}, please check your implementation.`); + return false; + } + return true; +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests (validBidRequests) { + const bids = []; + utils._each(validBidRequests, bidRequest => { + const timeout = config.getConfig('bidderTimeout'); + const { + bidId, + params = {}, + transactionId + } = bidRequest; + const data = {} + + if (params.inScreen) { + data.t = params.inScreen; + data.pi = 2; + } + if (params.inSlot) { + data.si = params.inSlot; + data.pi = 3; + } + + bids.push({ + id: bidId, + tmax: timeout, + tId: transactionId, + pi: data.pi, + sizes: bidRequest.sizes, + url: BID_ENDPOINT, + method: 'GET', + data: Object.assign(data, _getBrowserParams(), _getDigiTrustQueryParams()) + }) + }); + return bids; +} + +/** + * 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. + */ +function interpretResponse (serverResponse, bidRequest) { + const bidResponses = [] + const serverResponseBody = serverResponse.body + const { + ad: { + price: cpm, + id: creativeId, + markup + }, + cw: wrapper + } = serverResponseBody + let [width, height] = bidRequest.sizes[0] + + // we have to determine what product the request was for to know which loader to use. + // for now use inSlotLoader + if (creativeId) { + bidResponses.push({ + // dealId: DEAL_ID, + // referrer: REFERER, + ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, + bidderCode: spec.code, + cpm, + creativeId, + currency: 'USD', + height, + netRevenue: true, + requestId: bidRequest.id, + ttl: TIME_TO_LIVE, + width + }) + } + return bidResponses +} + +function getUserSyncs (syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: 'ADAPTER_SYNC_URL' + }] + } +} + +export const spec = { + code: BIDDER_CODE, + aliases: ALIAS_BIDDER_CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +} +registerBidder(spec) diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md new file mode 100644 index 00000000000..500c2a49e3b --- /dev/null +++ b/modules/gumgumBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: GumGum Bidder Adapter +Module Type: Bidder Adapter +Maintainer: engineering@gumgum.com +``` + +# Description + +GumGum adapter for Prebid.js 1.0 + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'gumgum', + params: { + inSlot: '9' // GumGum Slot ID given to the client + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: 'gumgum', + params: { + inScreen: 'ggumtest' // GumGum Zone ID given to the client + } + } + ] + } +]; +``` diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js new file mode 100644 index 00000000000..e24eda45783 --- /dev/null +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/gumgumBidAdapter'; + +const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; + +describe('gumgumAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'gumgum', + 'params': { + 'inScreen': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'gumgum', + 'params': { + 'inSlot': '9' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e' + } + ]; + + it('sends bid request to ENDPOINT via GET', () => { + const requests = spec.buildRequests(bidRequests); + const request = requests[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + expect(request.id).to.equal('30b31c1838de1e'); + }); + }) + + describe('interpretResponse', () => { + let serverResponse = { + 'ad': { + 'id': 29593, + 'width': 300, + 'height': 250, + 'ipd': 2000, + 'markup': '

I am an ad

', + 'ii': true, + 'du': null, + 'price': 0, + 'zi': 0, + 'impurl': 'http://g2.gumgum.com/ad/view', + 'clsurl': 'http://g2.gumgum.com/ad/close' + }, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let bidRequest = { + id: 12345, + sizes: [[300, 250]], + url: ENDPOINT, + method: 'GET', + pi: 3 + } + + it('should get correct bid response', () => { + let expectedResponse = { + 'ad': '

I am an ad

', + 'bidderCode': 'gumgum', + 'cpm': 0, + 'creativeId': 29593, + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'requestId': 12345, + 'width': 300, + // dealId: DEAL_ID, + // referrer: REFERER, + ttl: 60 + }; + expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); + }); + + it('handles nobid responses', () => { + let response = { + 'ad': {}, + 'pag': { + 't': 'ggumtest', + 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + 'css': 'html { overflow-y: auto }', + 'js': 'console.log("environment", env);' + }, + 'thms': 10000 + } + let result = spec.interpretResponse({ body: response }, bidRequest); + expect(result.length).to.equal(0); + }); + }) +}); From 21dbae585b508d425284c7671202a4ab1c77384a Mon Sep 17 00:00:00 2001 From: anthony Date: Fri, 5 Jan 2018 14:56:48 -0800 Subject: [PATCH 2/4] removed getUserSyncs. Give cpm a non-zero value when bidRequest is for test unit so DFP chooses it. --- modules/gumgumBidAdapter.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 400023e4630..8d90d8e0065 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -139,17 +139,16 @@ function interpretResponse (serverResponse, bidRequest) { }, cw: wrapper } = serverResponseBody + let isTestUnit = (bidRequest.data && bidRequest.data.pi === 3 && bidRequest.data.si === 9) let [width, height] = bidRequest.sizes[0] - // we have to determine what product the request was for to know which loader to use. - // for now use inSlotLoader if (creativeId) { bidResponses.push({ // dealId: DEAL_ID, // referrer: REFERER, ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, bidderCode: spec.code, - cpm, + cpm: isTestUnit ? 0.1 : cpm, creativeId, currency: 'USD', height, @@ -162,21 +161,11 @@ function interpretResponse (serverResponse, bidRequest) { return bidResponses } -function getUserSyncs (syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'ADAPTER_SYNC_URL' - }] - } -} - export const spec = { code: BIDDER_CODE, aliases: ALIAS_BIDDER_CODE, isBidRequestValid, buildRequests, - interpretResponse, - getUserSyncs + interpretResponse } registerBidder(spec) From 961f81e5f020db35d6fa591a316e397900ab723d Mon Sep 17 00:00:00 2001 From: anthony Date: Mon, 8 Jan 2018 18:12:13 -0800 Subject: [PATCH 3/4] parsing slot ID as integer from params --- modules/gumgumBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 8d90d8e0065..f14c7d4648d 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -104,7 +104,7 @@ function buildRequests (validBidRequests) { data.pi = 2; } if (params.inSlot) { - data.si = params.inSlot; + data.si = parseInt(params.inSlot, 10); data.pi = 3; } From b53be80273d9658b4a7f8992e3777ec2a6959654 Mon Sep 17 00:00:00 2001 From: anthony Date: Wed, 17 Jan 2018 18:21:56 -0800 Subject: [PATCH 4/4] ADSS-78 removed bidderCode from response. Correctly parsing bidRequest.sizes to account for 1-dimensional array. --- modules/gumgumBidAdapter.js | 3 +-- test/spec/modules/gumgumBidAdapter_spec.js | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index f14c7d4648d..2627b7417f5 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -140,14 +140,13 @@ function interpretResponse (serverResponse, bidRequest) { cw: wrapper } = serverResponseBody let isTestUnit = (bidRequest.data && bidRequest.data.pi === 3 && bidRequest.data.si === 9) - let [width, height] = bidRequest.sizes[0] + let [width, height] = utils.parseSizesInput(bidRequest.sizes)[0].split('x') if (creativeId) { bidResponses.push({ // dealId: DEAL_ID, // referrer: REFERER, ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, - bidderCode: spec.code, cpm: isTestUnit ? 0.1 : cpm, creativeId, currency: 'USD', diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index e24eda45783..30627d4d12d 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -106,14 +106,13 @@ describe('gumgumAdapter', () => { it('should get correct bid response', () => { let expectedResponse = { 'ad': '

I am an ad

', - 'bidderCode': 'gumgum', 'cpm': 0, 'creativeId': 29593, 'currency': 'USD', - 'height': 250, + 'height': '250', 'netRevenue': true, 'requestId': 12345, - 'width': 300, + 'width': '300', // dealId: DEAL_ID, // referrer: REFERER, ttl: 60