-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rubicon analytics v2 #5698
Rubicon analytics v2 #5698
Changes from 13 commits
33ecab2
ebe5d8c
0c88c49
c72cd54
2161cc1
57195c2
ab19c72
3b112a4
dbe91ce
490757f
aa0197f
3be5987
a0996e5
9f6bb95
524a2aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,15 @@ import { ajax } from '../src/ajax.js'; | |
import { config } from '../src/config.js'; | ||
import * as utils from '../src/utils.js'; | ||
import { getGlobal } from '../src/prebidGlobal.js'; | ||
import { getStorageManager } from '../src/storageManager.js'; | ||
|
||
const RUBICON_GVL_ID = 52; | ||
export const storage = getStorageManager(RUBICON_GVL_ID, 'rubicon'); | ||
const COOKIE_NAME = 'rpaSession'; | ||
const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins | ||
const END_EXPIRE_TIME = 21600000; // 6 hours | ||
|
||
let prebidGlobal = getGlobal(); | ||
const { | ||
EVENTS: { | ||
AUCTION_INIT, | ||
|
@@ -38,6 +46,7 @@ const cache = { | |
auctions: {}, | ||
targeting: {}, | ||
timeouts: {}, | ||
gpt: {}, | ||
}; | ||
|
||
export function getHostNameFromReferer(referer) { | ||
|
@@ -126,13 +135,15 @@ function sendMessage(auctionId, bidWonId) { | |
}); | ||
} | ||
let auctionCache = cache.auctions[auctionId]; | ||
let referrer = config.getConfig('pageUrl') || auctionCache.referrer; | ||
let referrer = config.getConfig('pageUrl') || (auctionCache && auctionCache.referrer); | ||
let message = { | ||
eventTimeMillis: Date.now(), | ||
integration: config.getConfig('rubicon.int_type') || DEFAULT_INTEGRATION, | ||
ruleId: config.getConfig('rubicon.rule_name'), | ||
version: '$prebid.version$', | ||
referrerUri: referrer, | ||
referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer) | ||
referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), | ||
channel: 'web' | ||
}; | ||
const wrapperName = config.getConfig('rubicon.wrapperName'); | ||
if (wrapperName) { | ||
|
@@ -149,7 +160,8 @@ function sendMessage(auctionId, bidWonId) { | |
'mediaTypes', | ||
'dimensions', | ||
'adserverTargeting', () => stringProperties(cache.targeting[bid.adUnit.adUnitCode] || {}), | ||
'adSlot' | ||
'gam', | ||
'pbAdSlot' | ||
]); | ||
adUnit.bids = []; | ||
adUnit.status = 'no-bid'; // default it to be no bid | ||
|
@@ -215,6 +227,30 @@ function sendMessage(auctionId, bidWonId) { | |
} | ||
} | ||
|
||
// gather gdpr info | ||
if (auctionCache.gdprConsent) { | ||
auction.gdpr = utils.pick(auctionCache.gdprConsent, [ | ||
'gdprApplies as applies', | ||
'consentString', | ||
'apiVersion as version' | ||
]); | ||
} | ||
|
||
// gather session info | ||
if (auctionCache.session) { | ||
message.session = utils.pick(auctionCache.session, [ | ||
'id', | ||
'pvid', | ||
'start', | ||
'expires' | ||
]); | ||
if (!utils.isEmpty(auctionCache.session.fpkvs)) { | ||
message.fpkvs = Object.keys(auctionCache.session.fpkvs).map(key => { | ||
return { key, value: auctionCache.session.fpkvs[key] }; | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mapping into array of objects of { key, value } requested by internal team |
||
} | ||
} | ||
|
||
if (serverConfig) { | ||
auction.serverTimeoutMillis = serverConfig.timeout; | ||
} | ||
|
@@ -272,7 +308,7 @@ function getBidPrice(bid) { | |
} | ||
// otherwise we convert and return | ||
try { | ||
return Number(getGlobal().convertCurrency(cpm, currency, 'USD')); | ||
return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); | ||
} catch (err) { | ||
utils.logWarn('Rubicon Analytics Adapter: Could not determine the bidPriceUSD of the bid ', bid); | ||
} | ||
|
@@ -300,6 +336,19 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { | |
]); | ||
} | ||
|
||
function getDynamicKvps() { | ||
if (prebidGlobal.rp && typeof prebidGlobal.rp.getCustomTargeting === 'function') { | ||
return prebidGlobal.rp.getCustomTargeting(); | ||
} | ||
return {}; | ||
} | ||
|
||
function getPageViewId() { | ||
if (prebidGlobal.rp && typeof prebidGlobal.rp.generatePageViewId === 'function') { | ||
return prebidGlobal.rp.generatePageViewId(false); | ||
} | ||
} | ||
|
||
let samplingFactor = 1; | ||
let accountId; | ||
// List of known rubicon aliases | ||
|
@@ -318,6 +367,74 @@ function setRubiconAliases(aliasRegistry) { | |
}); | ||
} | ||
|
||
function getRpaCookie() { | ||
let encodedCookie = storage.getCookie(COOKIE_NAME); | ||
if (encodedCookie) { | ||
try { | ||
return JSON.parse(window.atob(encodedCookie)); | ||
} catch (e) { | ||
utils.logError(`Rubicon Analytics: Unable to decode ${COOKIE_NAME} value: `, e); | ||
} | ||
} | ||
return {}; | ||
} | ||
|
||
function setRpaCookie(decodedCookie) { | ||
try { | ||
storage.setCookie(COOKIE_NAME, window.btoa(JSON.stringify(decodedCookie))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated to localStorage |
||
} catch (e) { | ||
utils.logError(`Rubicon Analytics: Unable to encode ${COOKIE_NAME} value: `, e); | ||
} | ||
} | ||
|
||
function updateRpaCookie() { | ||
const currentTime = Date.now(); | ||
let decodedRpaCookie = getRpaCookie(); | ||
if ( | ||
!Object.keys(decodedRpaCookie).length || | ||
(currentTime - decodedRpaCookie.lastSeen) > LAST_SEEN_EXPIRE_TIME || | ||
decodedRpaCookie.expires < currentTime | ||
) { | ||
decodedRpaCookie = { | ||
id: utils.generateUUID(), | ||
start: currentTime, | ||
expires: currentTime + END_EXPIRE_TIME, // six hours later, | ||
} | ||
} | ||
// possible that decodedRpaCookie is undefined, and if it is, we probably are blocked by storage or some other exception | ||
if (Object.keys(decodedRpaCookie).length) { | ||
decodedRpaCookie.lastSeen = currentTime; | ||
decodedRpaCookie.fpkvs = {...decodedRpaCookie.fpkvs, ...getDynamicKvps()}; | ||
decodedRpaCookie.pvid = getPageViewId(); | ||
setRpaCookie(decodedRpaCookie) | ||
} | ||
return decodedRpaCookie; | ||
} | ||
|
||
function subscribeToGamSlots() { | ||
window.googletag.pubads().addEventListener('slotRenderEnded', event => { | ||
const isMatchingAdSlot = utils.isAdUnitCodeMatchingSlot(event.slot); | ||
// loop through auctions and adUnits and mark the info | ||
Object.keys(cache.auctions).forEach(auctionId => { | ||
(Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { | ||
let bid = cache.auctions[auctionId].bids[bidId]; | ||
// if this slot matches this bids adUnit, add the adUnit info | ||
if (isMatchingAdSlot(bid.adUnit.adUnitCode)) { | ||
bid.adUnit.gam = utils.pick(event, [ | ||
// these come in as `null` from Gpt, which when stringified does not get removed | ||
// so set explicitly to undefined when not a number | ||
'advertiserId', advertiserId => utils.isNumber(advertiserId) ? advertiserId : undefined, | ||
'creativeId', creativeId => utils.isNumber(creativeId) ? creativeId : undefined, | ||
'lineItemId', lineItemId => utils.isNumber(lineItemId) ? lineItemId : undefined, | ||
'adSlot', () => event.slot.getAdUnitPath(), | ||
'isSlotEmpty', () => event.isEmpty || undefined | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WE only want to send this if slot.isEmpty is true. If the slot is NOT empty, then there will be the advertiserIds etc, so not necessary to send. |
||
]); | ||
} | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
let baseAdapter = adapter({analyticsType: 'endpoint'}); | ||
let rubiconAdapter = Object.assign({}, baseAdapter, { | ||
referrerHostname: '', | ||
|
@@ -376,12 +493,18 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { | |
]); | ||
cacheEntry.bids = {}; | ||
cacheEntry.bidsWon = {}; | ||
cacheEntry.referrer = args.bidderRequests[0].refererInfo.referer; | ||
cacheEntry.referrer = utils.deepAccess(args, 'bidderRequests.0.refererInfo.referer'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deep acess so we do not throw runtime undefined errors |
||
const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); | ||
if (floorData) { | ||
cacheEntry.floorData = {...floorData}; | ||
} | ||
cacheEntry.gdprConsent = utils.deepAccess(args, 'bidderRequests.0.gdprConsent'); | ||
cacheEntry.session = storage.cookiesAreEnabled() && updateRpaCookie(); | ||
cache.auctions[args.auctionId] = cacheEntry; | ||
// register to listen to gpt events if not done yet | ||
if (!cache.gpt.registered && utils.isGptPubadsDefined()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is not, must have left it out or removed it accidentally! Thanks, good catch! |
||
subscribeToGamSlots(); | ||
} | ||
break; | ||
case BID_REQUESTED: | ||
Object.assign(cache.auctions[args.auctionId].bids, args.bids.reduce((memo, bid) => { | ||
|
@@ -452,6 +575,12 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { | |
} | ||
return ['banner']; | ||
}, | ||
'gam', () => { | ||
if (utils.deepAccess(bid, 'fpd.context.adServer.name') === 'gam') { | ||
return {adSlot: bid.fpd.context.adServer.adSlot} | ||
} | ||
}, | ||
'pbAdSlot', () => utils.deepAccess(bid, 'fpd.context.pbAdSlot') | ||
]) | ||
]); | ||
return memo; | ||
|
@@ -461,8 +590,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { | |
let auctionEntry = cache.auctions[args.auctionId]; | ||
let bid = auctionEntry.bids[args.requestId]; | ||
// If floor resolved gptSlot but we have not yet, then update the adUnit to have the adSlot name | ||
if (!utils.deepAccess(bid, 'adUnit.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { | ||
bid.adUnit.adSlot = args.floorData.matchedFields.gptSlot; | ||
if (!utils.deepAccess(bid, 'adUnit.gam.adSlot') && utils.deepAccess(args, 'floorData.matchedFields.gptSlot')) { | ||
utils.deepSetValue(bid, 'adUnit.gam.adSlot', args.floorData.matchedFields.gptSlot); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sending There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/prebid/Prebid.js/pull/5698/files#diff-7eda04fa0d03f759128e98228a4d9ed5R583 Set just above. 11 lines |
||
} | ||
// if we have not set enforcements yet set it | ||
if (!utils.deepAccess(auctionEntry, 'floorData.enforcements') && utils.deepAccess(args, 'floorData.enforcements')) { | ||
|
@@ -479,7 +608,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { | |
delete bid.error; // it's possible for this to be set by a previous timeout | ||
break; | ||
case NO_BID: | ||
bid.status = args.status === BID_REJECTED ? 'rejected' : 'no-bid'; | ||
bid.status = args.status === BID_REJECTED ? 'rejected-ipf' : 'no-bid'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There will be future rejected reasons, so updating with -ipf now. But I'll make it a constant. |
||
delete bid.error; | ||
break; | ||
default: | ||
|
@@ -488,7 +617,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { | |
code: 'request-error' | ||
}; | ||
} | ||
bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp; | ||
bid.clientLatencyMillis = bid.timeToRespond || Date.now() - cache.auctions[args.auctionId].timestamp; | ||
bid.bidResponse = parseBidResponse(args, bid.bidResponse); | ||
break; | ||
case BIDDER_DONE: | ||
|
@@ -549,7 +678,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { | |
adapterManager.registerAnalyticsAdapter({ | ||
adapter: rubiconAdapter, | ||
code: 'rubicon', | ||
gvlid: 52 | ||
gvlid: RUBICON_GVL_ID | ||
}); | ||
|
||
export default rubiconAdapter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handle if this is undefinied in some cases