Skip to content

Commit

Permalink
sovrn analytics adapter with 3.0 updates (prebid#4620)
Browse files Browse the repository at this point in the history
  • Loading branch information
aprakash-sovrn authored and Isaac A. Dettman committed Dec 18, 2019
1 parent aa63645 commit 7682dd5
Show file tree
Hide file tree
Showing 2 changed files with 871 additions and 0 deletions.
325 changes: 325 additions & 0 deletions modules/sovrnAnalyticsAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
import adapter from '../src/AnalyticsAdapter'
import adaptermanager from '../src/adapterManager'
import CONSTANTS from '../src/constants.json'
import {ajaxBuilder} from '../src/ajax'
import * as utils from '../src/utils'
import {config} from '../src/config'
import find from 'core-js/library/fn/array/find'
import includes from 'core-js/library/fn/array/includes'

const ajax = ajaxBuilder(0)

const {
EVENTS: {
AUCTION_END,
BID_REQUESTED,
BID_ADJUSTMENT,
BID_RESPONSE,
BID_WON
}
} = CONSTANTS

let pbaUrl = 'https://pba.aws.lijit.com/analytics'
let currentAuctions = {};
const analyticsType = 'endpoint'

const getClosestTop = () => {
let topFrame = window;
let err = false;
try {
while (topFrame.parent.document !== topFrame.document) {
if (topFrame.parent.document) {
topFrame = topFrame.parent;
} else {
throw new Error();
}
}
} catch (e) {
bException = true;
}

return {
topFrame,
err
};
};

const getBestPageUrl = ({err: crossDomainError, topFrame}) => {
let sBestPageUrl = '';

if (!crossDomainError) {
// easy case- we can get top frame location
sBestPageUrl = topFrame.location.href;
} else {
try {
try {
sBestPageUrl = window.top.location.href;
} catch (e) {
let aOrigins = window.location.ancestorOrigins;
sBestPageUrl = aOrigins[aOrigins.length - 1];
}
} catch (e) {
sBestPageUrl = topFrame.document.referrer;
}
}

return sBestPageUrl;
};
const rootURL = getBestPageUrl(getClosestTop())

let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), {
track({ eventType, args }) {
try {
if (eventType === BID_WON) {
new BidWinner(this.sovrnId, args).send();
return
}
if (args && args.auctionId && currentAuctions[args.auctionId] && currentAuctions[args.auctionId].status === 'complete') {
throw new Error('Event Received after Auction Close Auction Id ' + args.auctionId)
}
if (args && args.auctionId && currentAuctions[args.auctionId] === undefined) {
currentAuctions[args.auctionId] = new AuctionData(this.sovrnId, args.auctionId)
}
switch (eventType) {
case BID_REQUESTED:
console.log(args)
currentAuctions[args.auctionId].bidRequested(args)
break
case BID_ADJUSTMENT:
currentAuctions[args.auctionId].originalBid(args)
break
case BID_RESPONSE:
currentAuctions[args.auctionId].adjustedBid(args)
break
case AUCTION_END:
currentAuctions[args.auctionId].send();
break
}
} catch (e) {
new LogError(e, this.sovrnId, {eventType, args}).send()
}
},
})

sovrnAnalyticsAdapter.getAuctions = function () {
return currentAuctions;
};

sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics;

// override enableAnalytics so we can get access to the config passed in from the page
sovrnAnalyticsAdapter.enableAnalytics = function (config) {
let sovrnId = ''
if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) {
sovrnId = config.options.sovrnId || config.options.affiliateId;
} else {
utils.logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.')
return
}
sovrnAnalyticsAdapter.sovrnId = sovrnId;
if (config.options.pbaUrl) {
pbaUrl = config.options.pbaUrl;
}
sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function
};

adaptermanager.registerAnalyticsAdapter({
adapter: sovrnAnalyticsAdapter,
code: 'sovrn'
});

/** Class Representing a Winning Bid */
class BidWinner {
/**
* Creates a new bid winner
* @param {string} sovrnId - the affiliate id from the analytics config
* @param {*} event - the args object from the auction event
*/
constructor(sovrnId, event) {
this.body = {}
this.body.prebidVersion = $$REPO_AND_VERSION$$
this.body.sovrnId = sovrnId
this.body.winningBid = JSON.parse(JSON.stringify(event))
this.body.url = rootURL
this.body.payload = 'winner'
delete this.body.winningBid.ad
}

/**
* Sends the auction to the the ingest server
*/
send() {
this.body.ts = utils.timestamp()
ajax(
pbaUrl,
null,
JSON.stringify(this.body),
{
contentType: 'application/json',
method: 'POST',
}
)
}
}

/** Class representing an Auction */
class AuctionData {
/**
* Create a new auction data collector
* @param {string} sovrnId - the affiliate id from the analytics config
* @param {string} auctionId - the auction id from the auction event
*/
constructor(sovrnId, auctionId) {
this.auction = {}
this.auction.prebidVersion = $$REPO_AND_VERSION$$
this.auction.sovrnId = sovrnId
this.auction.auctionId = auctionId
this.auction.payload = 'auction'
this.auction.timeouts = {
buffer: config.getConfig('timeoutBuffer'),
bidder: config.getConfig('bidderTimeout'),
}
this.auction.priceGranularity = config.getConfig('priceGranularity')
this.auction.url = rootURL
this.auction.requests = []
this.auction.unsynced = []
this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode']

setTimeout(function(id) {
delete currentAuctions[id]
}, 300000, this.auction.auctionId)
}

/**
* Record a bid request event
* @param {*} event - the args object from the auction event
*/
bidRequested(event) {
const eventCopy = JSON.parse(JSON.stringify(event))
delete eventCopy.doneCbCallCount
delete eventCopy.auctionId
this.auction.requests.push(eventCopy)
}

/**
* Finds the bid from the auction that the event is associated with
* @param {*} event - the args object from the auction event
* @return {*} - the bid
*/
findBid(event) {
const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode))
if (!bidder) {
this.auction.unsynced.push(JSON.parse(JSON.stringify(event)))
}
let bid = find(bidder.bids, b => (b.bidId === event.requestId))

if (!bid) {
event.unmatched = true
bidder.bids.push(JSON.parse(JSON.stringify(event)))
}
return bid
}

/**
* Records the original bid before any adjustments have been made
* @param {*} event - the args object from the auction event
* NOTE: the bid adjustment occurs before the bid response
* the bid adjustment seems to be the bid ready to be adjusted
*/
originalBid(event) {
let bid = this.findBid(event)
if (bid) {
Object.assign(bid, JSON.parse(JSON.stringify(event)))
this.dropBidFields.forEach((f) => delete bid[f])
}
}

/**
* Replaces original values with adjusted values and records the original values for changed values
* in bid.originalValues
* @param {*} event - the args object from the auction event
*/
adjustedBid(event) {
let bid = this.findBid(event)
if (bid) {
bid.originalValues = Object.keys(event).reduce((o, k) => {
if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !includes(this.dropBidFields, k)) {
o[k] = bid[k]
bid[k] = event[k]
}
return o
}, {})
}
}

/**
* Sends the auction to the the ingest server
*/
send() {
let maxBids = {}
this.auction.requests.forEach(request => {
request.bids.forEach(bid => {
maxBids[bid.adUnitCode] = maxBids[bid.adUnitCode] || {cpm: 0}
if (bid.cpm > maxBids[bid.adUnitCode].cpm) {
maxBids[bid.adUnitCode] = bid
}
})
})
Object.keys(maxBids).forEach(unit => {
maxBids[unit].isAuctionWinner = true
})
this.auction.ts = utils.timestamp()
ajax(
pbaUrl,
() => {
currentAuctions[this.auction.auctionId] = {status: 'complete', auctionId: this.auction.auctionId}
},
JSON.stringify(this.auction),
{
contentType: 'application/json',
method: 'POST',
}
)
}
}
class LogError {
constructor(e, sovrnId, data) {
this.error = {}
this.error.payload = 'error'
this.error.message = e.message
this.error.stack = e.stack
this.error.data = data
this.error.prebidVersion = $$REPO_AND_VERSION$$
this.error.sovrnId = sovrnId
this.error.url = rootURL
this.error.userAgent = navigator.userAgent
}
send() {
if (this.error.data && this.error.data.requests) {
this.error.data.requests.forEach(request => {
if (request.bids) {
request.bids.forEach(bid => {
if (bid.ad) {
delete bid.ad
}
})
}
})
}
if (ErrorEvent.data && error.data.ad) {
delete error.data.ad
}
this.error.ts = utils.timestamp()
ajax(
pbaUrl,
null,
JSON.stringify(this.error),
{
contentType: 'application/json',
method: 'POST',
}
)
}
}

export default sovrnAnalyticsAdapter;
Loading

0 comments on commit 7682dd5

Please sign in to comment.