Skip to content

Commit

Permalink
GumGum Adapter for Prebid.js 1.0 (prebid#1966)
Browse files Browse the repository at this point in the history
* GumGum Adapter for Prebid.js 1.0

* removed getUserSyncs. Give cpm a non-zero value when bidRequest is for test unit so DFP chooses it.

* parsing slot ID as integer from params

* ADSS-78 removed bidderCode from response. Correctly parsing bidRequest.sizes to account for 1-dimensional array.
  • Loading branch information
mxcoder authored and ehoch committed Jan 29, 2018
1 parent 96b45e3 commit b78cfd9
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 442 deletions.
320 changes: 157 additions & 163 deletions modules/gumgumBidAdapter.js
Original file line number Diff line number Diff line change
@@ -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 = `<script>
(function (context, topWindow, d, s, G) {
G = topWindow.GUMGUM;
d = topWindow.document;
function loadAd() {
topWindow.GUMGUM.pbjs("${trackingId}", ${productId}, "${encodedResponse}" , context);
}
if (G) {
loadAd();
} else {
topWindow.$$PREBID_GLOBAL$$.loadScript("https://js.gumgum.com/services.js", loadAd);
}
}(window, top));
</script>`;
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)
40 changes: 40 additions & 0 deletions modules/gumgumBidAdapter.md
Original file line number Diff line number Diff line change
@@ -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
}
}
]
}
];
```
Loading

0 comments on commit b78cfd9

Please sign in to comment.