diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 521dfabfb2d..2627b7417f5 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -1,176 +1,170 @@ -const bidfactory = require('src/bidfactory'); -const bidmanager = require('src/bidmanager'); -const utils = require('src/utils'); -const adloader = require('src/adloader'); -var adaptermanager = require('src/adaptermanager'); - -const BIDDER_CODE = 'gumgum'; -const CALLBACKS = {}; - -const GumgumAdapter = function GumgumAdapter() { - const bidEndpoint = `https://g2.gumgum.com/hbid/imp`; - - let topWindow; - let topScreen; - let pageViewId; - const requestCache = {}; - const throttleTable = {}; - const defaultThrottle = 3e4; - const dtCredentials = { member: 'YcXr87z2lpbB' }; +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) { - return utils.logError(error); - } - - function _getTimeStamp() { - return new Date().getTime(); - } - - function _getDigiTrustQueryParams() { - function getDigiTrustId () { - var digiTrustUser = (window.DigiTrust && window.DigiTrust.getUser) ? window.DigiTrust.getUser(dtCredentials) : {}; - 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 - }; + utils.logError(error); + return browserParams } - function _callBids({ bids }) { - const browserParams = { - vw: topWindow.innerWidth, - vh: topWindow.innerHeight, - sw: topScreen.width, - sh: topScreen.height, - pu: topWindow.location.href, - ce: navigator.cookieEnabled, - dpr: topWindow.devicePixelRatio || 1 - }; - - utils._each(bids, bidRequest => { - const { bidId - , params = {} - , placementCode - } = bidRequest; - const timestamp = _getTimeStamp(); - const trackingId = params.inScreen; - const nativeId = params['native']; - const slotId = params.inSlot; - const bid = { tmax: $$PREBID_GLOBAL$$.cbTimeout }; - - /* slot/native ads need the placement id */ - switch (true) { - case !!(params.inImage): bid.pi = 1; break; - case !!(params.inScreen): bid.pi = 2; break; - case !!(params.inSlot): bid.pi = 3; break; - case !!(params['native']): bid.pi = 5; break; - default: return utils.logWarn( - `[GumGum] No product selected for the placement ${placementCode}` + - ', please check your implementation.' - ); - } - - /* throttle based on the latest request for this product */ - const productId = bid.pi; - const requestKey = productId + '|' + placementCode; - const throttle = throttleTable[productId]; - const latestRequest = requestCache[requestKey]; - if (latestRequest && throttle && (timestamp - latestRequest) < throttle) { - return utils.logWarn( - `[GumGum] The refreshes for "${placementCode}" with the params ` + - `${JSON.stringify(params)} should be at least ${throttle / 1e3}s apart.` - ); - } - /* update the last request */ - requestCache[requestKey] = timestamp; - - /* tracking id is required for in-image and in-screen */ - if (trackingId) bid.t = trackingId; - /* native ads require a native placement id */ - if (nativeId) bid.ni = nativeId; - /* slot ads require a slot id */ - if (slotId) bid.si = slotId; - - /* include the pageViewId, if any */ - if (pageViewId) bid.pv = pageViewId; - - const cachedBid = Object.assign({ - placementCode, - id: bidId - }, bid); - - const callback = { jsonp: `$$PREBID_GLOBAL$$.handleGumGumCB['${bidId}']` }; - CALLBACKS[bidId] = _handleGumGumResponse(cachedBid); - const query = Object.assign(callback, browserParams, bid, _getDigiTrustQueryParams()); - const bidCall = `${bidEndpoint}?${utils.parseQueryStringParameters(query)}`; - adloader.loadScript(bidCall); - }); + browserParams = { + vw: topWindow.innerWidth, + vh: topWindow.innerHeight, + sw: topScreen.width, + sh: topScreen.height, + pu: utils.getTopWindowUrl(), + ce: utils.cookiesAreEnabled(), + dpr: topWindow.devicePixelRatio || 1 } - - const _handleGumGumResponse = cachedBidRequest => (bidResponse = {}) => { - const { pi: productId - } = cachedBidRequest; - const { ad = {} - , pag = {} - , thms: throttle - } = bidResponse; - /* cache the pageViewId */ - if (pag && pag.pvid) pageViewId = pag.pvid; - if (ad && ad.id) { - /* set the new throttle */ - throttleTable[productId] = throttle || defaultThrottle; - /* create the bid */ - const bid = bidfactory.createBid(1); - const { t: trackingId - } = pag; - bidResponse.request = cachedBidRequest; - const encodedResponse = encodeURIComponent(JSON.stringify(bidResponse)); - const gumgumAdLoader = ``; - Object.assign(bid, { - cpm: ad.price, - ad: gumgumAdLoader, - width: ad.width, - height: ad.height, - bidderCode: BIDDER_CODE - }); - bidmanager.addBidResponse(cachedBidRequest.placementCode, bid); - } else { - const noBid = bidfactory.createBid(2); - noBid.bidderCode = BIDDER_CODE; - bidmanager.addBidResponse(cachedBidRequest.placementCode, noBid); - } - delete CALLBACKS[cachedBidRequest.id]; + 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) || ''; }; - window.$$PREBID_GLOBAL$$.handleGumGumCB = CALLBACKS; - + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return {}; + } return { - callBids: _callBids + 'dt': digiTrustId.id }; -}; - -adaptermanager.registerBidAdapter(new GumgumAdapter(), 'gumgum'); +} + +/** + * 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 = parseInt(params.inSlot, 10); + data.pi = 3; + } -module.exports = GumgumAdapter; + 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 isTestUnit = (bidRequest.data && bidRequest.data.pi === 3 && bidRequest.data.si === 9) + 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, + cpm: isTestUnit ? 0.1 : cpm, + creativeId, + currency: 'USD', + height, + netRevenue: true, + requestId: bidRequest.id, + ttl: TIME_TO_LIVE, + width + }) + } + return bidResponses +} + +export const spec = { + code: BIDDER_CODE, + aliases: ALIAS_BIDDER_CODE, + isBidRequestValid, + buildRequests, + interpretResponse +} +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 index b90a1a48b15..30627d4d12d 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,295 +1,138 @@ -import {expect} from 'chai'; -import Adapter from '../../../modules/gumgumBidAdapter'; -import bidManager from '../../../src/bidmanager'; -import adLoader from '../../../src/adloader'; -import * as utils from '../../../src/utils'; -import { STATUS } from '../../../src/constants'; +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/gumgumBidAdapter'; -describe('gumgum adapter', () => { - 'use strict'; +const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; - let adapter; - let sandbox; +describe('gumgumAdapter', () => { + const adapter = newBidder(spec); - const TEST = { - PUBLISHER_IDENTITY: 'ggumtest', - BIDDER_CODE: 'gumgum', - PLACEMENT: 'placementId', - CPM: 2 - }; - const bidderRequest = { - bidderCode: TEST.BIDDER_CODE, - bids: [{ // in-screen - bidId: 'InScreenBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - inScreen: TEST.PUBLISHER_IDENTITY - } - }, { // in-image - bidId: 'InImageBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - inImage: TEST.PUBLISHER_IDENTITY - } - }, { // native - bidId: 'NativeBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - native: 10 - } - }, { // slot - bidId: 'InSlotBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ], - params: { - inSlot: 10 - } - }, { // no identity - bidId: 'NoIdentityBidId', - bidder: TEST.BIDDER_CODE, - placementCode: TEST.PLACEMENT, - sizes: [ [728, 90] ] - }] - }; - const pageParams = { - 'pvid': 'PVID' - }; - const bidderResponse = { - 'ad': { - 'id': 1, - 'width': 728, - 'height': 90, - 'markup': '