forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Glimpse: update api and request shape, optimize and refactor (prebid#…
…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
Showing
3 changed files
with
473 additions
and
393 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.