Skip to content

Commit

Permalink
Glimpse: update api and request shape, optimize and refactor (prebid#…
Browse files Browse the repository at this point in the history
…8237)

- Update Glimpse prebid api endpoint
- Pass gvlid to storage manager for gdpr use cases
- Include consent info in query params for better efficiency and privacy on the handling of user data
- Update the request body shape to optimize the size of the request
- update fpd shape and optimize fpd object
- Add appendQueryParam and optimizeObject util functions
- Remove unnecessary util functions
- Refactor and rename of some util functions for improved simplicity
- Rename variables for clarity
- Update jsdoc comments
- Reformat the files
- Update test spec: add fpd unit test and remove unnecessary tests
  • Loading branch information
eddyu0 committed Apr 4, 2022
1 parent f66e347 commit 28b4c69
Show file tree
Hide file tree
Showing 3 changed files with 473 additions and 393 deletions.
265 changes: 118 additions & 147 deletions modules/glimpseBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,230 +1,201 @@
import { BANNER } from '../src/mediaTypes.js'
import { config } from '../src/config.js'
import { getStorageManager } from '../src/storageManager.js'
import { isArray } from '../src/utils.js'
import { registerBidder } from '../src/adapters/bidderFactory.js'

const GVLID = 1012
const BIDDER_CODE = 'glimpse'
const storageManager = getStorageManager({bidderCode: BIDDER_CODE})
const ENDPOINT = 'https://api.glimpsevault.io/ads/serving/public/v1/prebid'
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
import { BANNER } from '../src/mediaTypes.js';
import { getStorageManager } from '../src/storageManager.js';
import {
isArray,
isEmpty,
isEmptyStr,
isStr,
isPlainObject,
} from '../src/utils.js';

const GVLID = 1012;
const BIDDER_CODE = 'glimpse';
const storageManager = getStorageManager({
gvlid: GVLID,
bidderCode: BIDDER_CODE,
});
const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid';
const LOCAL_STORAGE_KEY = {
vault: {
jwt: 'gp_vault_jwt',
},
}
};

export const spec = {
gvlid: GVLID,
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],

/**
* Determines whether or not the given bid request is valid
* Determines if the bid request is valid
* @param bid {BidRequest} The bid to validate
* @return {boolean}
*/
isBidRequestValid: (bid) => {
return (
hasValue(bid) &&
hasValue(bid.params) &&
hasStringValue(bid.params.placementId)
)
const pid = bid?.params?.pid;
return isStr(pid) && !isEmptyStr(pid);
},

/**
* Builds http request for Glimpse bids
* Builds the http request
* @param validBidRequests {BidRequest[]}
* @param bidderRequest {BidderRequest}
* @returns {ServerRequest}
*/
buildRequests: (validBidRequests, bidderRequest) => {
const auth = getVaultJwt()
const referer = getReferer(bidderRequest)
const gdprConsent = getGdprConsentChoice(bidderRequest)
const bidRequests = validBidRequests.map(processBidRequest)
const firstPartyData = getFirstPartyData()
const url = buildQuery(bidderRequest);
const auth = getVaultJwt();
const referer = getReferer(bidderRequest);
const imp = validBidRequests.map(processBidRequest);
const fpd = getFirstPartyData();

const data = {
auth,
data: {
referer,
gdprConsent,
bidRequests,
site: firstPartyData.site,
user: firstPartyData.user,
bidderCode: spec.code,
}
}
imp,
fpd,
},
};

return {
method: 'POST',
url: ENDPOINT,
url,
data: JSON.stringify(data),
options: {},
}
};
},

/**
* Parse response from Glimpse server
* @param bidResponse {ServerResponse}
* Parse http response
* @param response {ServerResponse}
* @returns {Bid[]}
*/
interpretResponse: (bidResponse) => {
const isValidResponse = isValidBidResponse(bidResponse)

if (isValidResponse) {
const {auth, data} = bidResponse.body
setVaultJwt(auth)
return data.bids
interpretResponse: (response) => {
if (isValidResponse(response)) {
const { auth, data } = response.body;
setVaultJwt(auth);
const bids = data.bids.map(processBidResponse);
return bids;
}

return []
return [];
},
}
};

function setVaultJwt(auth) {
storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth)
storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth);
}

function getVaultJwt() {
return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || ''
return (
storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || ''
);
}

function getReferer(bidderRequest) {
const hasReferer =
hasValue(bidderRequest) &&
hasValue(bidderRequest.refererInfo) &&
hasStringValue(bidderRequest.refererInfo.referer)

if (hasReferer) {
return bidderRequest.refererInfo.referer
}

return ''
return bidderRequest?.refererInfo?.referer || '';
}

function getGdprConsentChoice(bidderRequest) {
const hasGdprConsent =
hasValue(bidderRequest) &&
hasValue(bidderRequest.gdprConsent)
function buildQuery(bidderRequest) {
let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$');

if (hasGdprConsent) {
const gdprConsent = bidderRequest.gdprConsent
const hasGdprApplies = hasBooleanValue(gdprConsent.gdprApplies)
const timeout = config.getConfig('bidderTimeout');
url = appendQueryParam(url, 'tmax', timeout);

return {
consentString: gdprConsent.consentString || '',
vendorData: gdprConsent.vendorData || {},
gdprApplies: hasGdprApplies ? gdprConsent.gdprApplies : true,
}
if (gdprApplies(bidderRequest)) {
const consentString = bidderRequest.gdprConsent.consentString;
url = appendQueryParam(url, 'reg', 'gdpr');
url = appendQueryParam(url, 'cs', consentString);
} else if (ccpaApplies(bidderRequest)) {
url = appendQueryParam(url, 'reg', 'ccpa');
url = appendQueryParam(url, 'cs', bidderRequest.uspConsent);
} else {
url = appendQueryParam(url, 'reg', 'none');
}
return url;
}

return {
consentString: '',
vendorData: {},
gdprApplies: false,
function appendQueryParam(url, key, value) {
if (!value) {
return url;
}
const prefix = url.includes('?') ? '&' : '?';
return `${url}${prefix}${key}=${encodeURIComponent(value)}`;
}

function gdprApplies(bidderRequest) {
return Boolean(bidderRequest?.gdprConsent?.gdprApplies);
}

function processBidRequest(bidRequest) {
const demand = bidRequest.params.demand || 'glimpse'
const sizes = normalizeSizes(bidRequest.sizes)
const keywords = bidRequest.params.keywords || {}
function ccpaApplies(bidderRequest) {
return (
!isEmptyStr(bidderRequest.uspConsent) &&
bidderRequest.uspConsent.substr(1, 3) !== '---'
);
}

function processBidRequest(bid) {
const sizes = normalizeSizes(bid.sizes);

return {
demand,
bid: bid.bidId,
pid: bid.params.pid,
sizes,
keywords,
bidId: bidRequest.bidId,
placementId: bidRequest.params.placementId,
unitCode: bidRequest.adUnitCode,
}
};
}

function normalizeSizes(sizes) {
const isSingleSize =
isArray(sizes) &&
sizes.length === 2 &&
!isArray(sizes[0]) &&
!isArray(sizes[1])
!isArray(sizes[1]);

if (isSingleSize) {
return [sizes]
return [sizes];
}

return sizes
return sizes;
}

function getFirstPartyData() {
const siteKeywords = parseGlobalKeywords('site')
const userKeywords = parseGlobalKeywords('user')

const siteAttributes = getConfig('ortb2.site.ext.data', {})
const userAttributes = getConfig('ortb2.user.ext.data', {})

return {
site: {
keywords: siteKeywords,
attributes: siteAttributes,
},
user: {
keywords: userKeywords,
attributes: userAttributes,
},
}
let fpd = config.getConfig('ortb2') || {};
optimizeObject(fpd);
return fpd;
}

function parseGlobalKeywords(scope) {
const keywords = getConfig(`ortb2.${scope}.keywords`, '')

return keywords
.split(', ')
.filter((keyword) => keyword !== '')
}

function getConfig(path, defaultValue) {
return config.getConfig(path) || defaultValue
}

function isValidBidResponse(bidResponse) {
return (
hasValue(bidResponse) &&
hasValue(bidResponse.body) &&
hasValue(bidResponse.body.data) &&
hasArrayValue(bidResponse.body.data.bids) &&
hasStringValue(bidResponse.body.auth)
)
}

function hasValue(value) {
return (
value !== undefined &&
value !== null
)
function optimizeObject(obj) {
if (!isPlainObject(obj)) {
return;
}
for (const [key, value] of Object.entries(obj)) {
optimizeObject(value);
// only delete empty object, array, or string
if (
(isPlainObject(value) || isArray(value) || isStr(value)) &&
isEmpty(value)
) {
delete obj[key];
}
}
}

function hasBooleanValue(value) {
return (
hasValue(value) &&
typeof value === 'boolean'
)
function isValidResponse(bidResponse) {
const auth = bidResponse?.body?.auth;
const bids = bidResponse?.body?.data?.bids;
return isStr(auth) && isArray(bids) && !isEmpty(bids);
}

function hasStringValue(value) {
return (
hasValue(value) &&
typeof value === 'string' &&
value.length > 0
)
}
function processBidResponse(bid) {
const meta = bid.meta || {};
meta.advertiserDomains = bid.meta?.advertiserDomains || [];

function hasArrayValue(value) {
return (
hasValue(value) &&
isArray(value) &&
value.length > 0
)
return {
...bid,
meta,
};
}

registerBidder(spec)
registerBidder(spec);
15 changes: 7 additions & 8 deletions modules/glimpseBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ const adUnits = [
sizes: [[300, 250]],
},
},
bids: [{
bidder: 'glimpse',
params: {
placementId: 'e53a7f564f8f44cc913b',
keywords: {
country: 'uk',
bids: [
{
bidder: 'glimpse',
params: {
pid: 'e53a7f564f8f44cc913b',
},
},
}],
],
},
]
];
```
Loading

0 comments on commit 28b4c69

Please sign in to comment.