Skip to content

Commit 0954c4f

Browse files
aprakash-sovrntadam75
authored and
tadam75
committed
sovrn analytics adapter with 3.0 updates (prebid#4620)
1 parent 764a64d commit 0954c4f

File tree

2 files changed

+871
-0
lines changed

2 files changed

+871
-0
lines changed

modules/sovrnAnalyticsAdapter.js

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
import adapter from '../src/AnalyticsAdapter'
2+
import adaptermanager from '../src/adapterManager'
3+
import CONSTANTS from '../src/constants.json'
4+
import {ajaxBuilder} from '../src/ajax'
5+
import * as utils from '../src/utils'
6+
import {config} from '../src/config'
7+
import find from 'core-js/library/fn/array/find'
8+
import includes from 'core-js/library/fn/array/includes'
9+
10+
const ajax = ajaxBuilder(0)
11+
12+
const {
13+
EVENTS: {
14+
AUCTION_END,
15+
BID_REQUESTED,
16+
BID_ADJUSTMENT,
17+
BID_RESPONSE,
18+
BID_WON
19+
}
20+
} = CONSTANTS
21+
22+
let pbaUrl = 'https://pba.aws.lijit.com/analytics'
23+
let currentAuctions = {};
24+
const analyticsType = 'endpoint'
25+
26+
const getClosestTop = () => {
27+
let topFrame = window;
28+
let err = false;
29+
try {
30+
while (topFrame.parent.document !== topFrame.document) {
31+
if (topFrame.parent.document) {
32+
topFrame = topFrame.parent;
33+
} else {
34+
throw new Error();
35+
}
36+
}
37+
} catch (e) {
38+
bException = true;
39+
}
40+
41+
return {
42+
topFrame,
43+
err
44+
};
45+
};
46+
47+
const getBestPageUrl = ({err: crossDomainError, topFrame}) => {
48+
let sBestPageUrl = '';
49+
50+
if (!crossDomainError) {
51+
// easy case- we can get top frame location
52+
sBestPageUrl = topFrame.location.href;
53+
} else {
54+
try {
55+
try {
56+
sBestPageUrl = window.top.location.href;
57+
} catch (e) {
58+
let aOrigins = window.location.ancestorOrigins;
59+
sBestPageUrl = aOrigins[aOrigins.length - 1];
60+
}
61+
} catch (e) {
62+
sBestPageUrl = topFrame.document.referrer;
63+
}
64+
}
65+
66+
return sBestPageUrl;
67+
};
68+
const rootURL = getBestPageUrl(getClosestTop())
69+
70+
let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), {
71+
track({ eventType, args }) {
72+
try {
73+
if (eventType === BID_WON) {
74+
new BidWinner(this.sovrnId, args).send();
75+
return
76+
}
77+
if (args && args.auctionId && currentAuctions[args.auctionId] && currentAuctions[args.auctionId].status === 'complete') {
78+
throw new Error('Event Received after Auction Close Auction Id ' + args.auctionId)
79+
}
80+
if (args && args.auctionId && currentAuctions[args.auctionId] === undefined) {
81+
currentAuctions[args.auctionId] = new AuctionData(this.sovrnId, args.auctionId)
82+
}
83+
switch (eventType) {
84+
case BID_REQUESTED:
85+
console.log(args)
86+
currentAuctions[args.auctionId].bidRequested(args)
87+
break
88+
case BID_ADJUSTMENT:
89+
currentAuctions[args.auctionId].originalBid(args)
90+
break
91+
case BID_RESPONSE:
92+
currentAuctions[args.auctionId].adjustedBid(args)
93+
break
94+
case AUCTION_END:
95+
currentAuctions[args.auctionId].send();
96+
break
97+
}
98+
} catch (e) {
99+
new LogError(e, this.sovrnId, {eventType, args}).send()
100+
}
101+
},
102+
})
103+
104+
sovrnAnalyticsAdapter.getAuctions = function () {
105+
return currentAuctions;
106+
};
107+
108+
sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics;
109+
110+
// override enableAnalytics so we can get access to the config passed in from the page
111+
sovrnAnalyticsAdapter.enableAnalytics = function (config) {
112+
let sovrnId = ''
113+
if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) {
114+
sovrnId = config.options.sovrnId || config.options.affiliateId;
115+
} else {
116+
utils.logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.')
117+
return
118+
}
119+
sovrnAnalyticsAdapter.sovrnId = sovrnId;
120+
if (config.options.pbaUrl) {
121+
pbaUrl = config.options.pbaUrl;
122+
}
123+
sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function
124+
};
125+
126+
adaptermanager.registerAnalyticsAdapter({
127+
adapter: sovrnAnalyticsAdapter,
128+
code: 'sovrn'
129+
});
130+
131+
/** Class Representing a Winning Bid */
132+
class BidWinner {
133+
/**
134+
* Creates a new bid winner
135+
* @param {string} sovrnId - the affiliate id from the analytics config
136+
* @param {*} event - the args object from the auction event
137+
*/
138+
constructor(sovrnId, event) {
139+
this.body = {}
140+
this.body.prebidVersion = $$REPO_AND_VERSION$$
141+
this.body.sovrnId = sovrnId
142+
this.body.winningBid = JSON.parse(JSON.stringify(event))
143+
this.body.url = rootURL
144+
this.body.payload = 'winner'
145+
delete this.body.winningBid.ad
146+
}
147+
148+
/**
149+
* Sends the auction to the the ingest server
150+
*/
151+
send() {
152+
this.body.ts = utils.timestamp()
153+
ajax(
154+
pbaUrl,
155+
null,
156+
JSON.stringify(this.body),
157+
{
158+
contentType: 'application/json',
159+
method: 'POST',
160+
}
161+
)
162+
}
163+
}
164+
165+
/** Class representing an Auction */
166+
class AuctionData {
167+
/**
168+
* Create a new auction data collector
169+
* @param {string} sovrnId - the affiliate id from the analytics config
170+
* @param {string} auctionId - the auction id from the auction event
171+
*/
172+
constructor(sovrnId, auctionId) {
173+
this.auction = {}
174+
this.auction.prebidVersion = $$REPO_AND_VERSION$$
175+
this.auction.sovrnId = sovrnId
176+
this.auction.auctionId = auctionId
177+
this.auction.payload = 'auction'
178+
this.auction.timeouts = {
179+
buffer: config.getConfig('timeoutBuffer'),
180+
bidder: config.getConfig('bidderTimeout'),
181+
}
182+
this.auction.priceGranularity = config.getConfig('priceGranularity')
183+
this.auction.url = rootURL
184+
this.auction.requests = []
185+
this.auction.unsynced = []
186+
this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode']
187+
188+
setTimeout(function(id) {
189+
delete currentAuctions[id]
190+
}, 300000, this.auction.auctionId)
191+
}
192+
193+
/**
194+
* Record a bid request event
195+
* @param {*} event - the args object from the auction event
196+
*/
197+
bidRequested(event) {
198+
const eventCopy = JSON.parse(JSON.stringify(event))
199+
delete eventCopy.doneCbCallCount
200+
delete eventCopy.auctionId
201+
this.auction.requests.push(eventCopy)
202+
}
203+
204+
/**
205+
* Finds the bid from the auction that the event is associated with
206+
* @param {*} event - the args object from the auction event
207+
* @return {*} - the bid
208+
*/
209+
findBid(event) {
210+
const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode))
211+
if (!bidder) {
212+
this.auction.unsynced.push(JSON.parse(JSON.stringify(event)))
213+
}
214+
let bid = find(bidder.bids, b => (b.bidId === event.requestId))
215+
216+
if (!bid) {
217+
event.unmatched = true
218+
bidder.bids.push(JSON.parse(JSON.stringify(event)))
219+
}
220+
return bid
221+
}
222+
223+
/**
224+
* Records the original bid before any adjustments have been made
225+
* @param {*} event - the args object from the auction event
226+
* NOTE: the bid adjustment occurs before the bid response
227+
* the bid adjustment seems to be the bid ready to be adjusted
228+
*/
229+
originalBid(event) {
230+
let bid = this.findBid(event)
231+
if (bid) {
232+
Object.assign(bid, JSON.parse(JSON.stringify(event)))
233+
this.dropBidFields.forEach((f) => delete bid[f])
234+
}
235+
}
236+
237+
/**
238+
* Replaces original values with adjusted values and records the original values for changed values
239+
* in bid.originalValues
240+
* @param {*} event - the args object from the auction event
241+
*/
242+
adjustedBid(event) {
243+
let bid = this.findBid(event)
244+
if (bid) {
245+
bid.originalValues = Object.keys(event).reduce((o, k) => {
246+
if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !includes(this.dropBidFields, k)) {
247+
o[k] = bid[k]
248+
bid[k] = event[k]
249+
}
250+
return o
251+
}, {})
252+
}
253+
}
254+
255+
/**
256+
* Sends the auction to the the ingest server
257+
*/
258+
send() {
259+
let maxBids = {}
260+
this.auction.requests.forEach(request => {
261+
request.bids.forEach(bid => {
262+
maxBids[bid.adUnitCode] = maxBids[bid.adUnitCode] || {cpm: 0}
263+
if (bid.cpm > maxBids[bid.adUnitCode].cpm) {
264+
maxBids[bid.adUnitCode] = bid
265+
}
266+
})
267+
})
268+
Object.keys(maxBids).forEach(unit => {
269+
maxBids[unit].isAuctionWinner = true
270+
})
271+
this.auction.ts = utils.timestamp()
272+
ajax(
273+
pbaUrl,
274+
() => {
275+
currentAuctions[this.auction.auctionId] = {status: 'complete', auctionId: this.auction.auctionId}
276+
},
277+
JSON.stringify(this.auction),
278+
{
279+
contentType: 'application/json',
280+
method: 'POST',
281+
}
282+
)
283+
}
284+
}
285+
class LogError {
286+
constructor(e, sovrnId, data) {
287+
this.error = {}
288+
this.error.payload = 'error'
289+
this.error.message = e.message
290+
this.error.stack = e.stack
291+
this.error.data = data
292+
this.error.prebidVersion = $$REPO_AND_VERSION$$
293+
this.error.sovrnId = sovrnId
294+
this.error.url = rootURL
295+
this.error.userAgent = navigator.userAgent
296+
}
297+
send() {
298+
if (this.error.data && this.error.data.requests) {
299+
this.error.data.requests.forEach(request => {
300+
if (request.bids) {
301+
request.bids.forEach(bid => {
302+
if (bid.ad) {
303+
delete bid.ad
304+
}
305+
})
306+
}
307+
})
308+
}
309+
if (ErrorEvent.data && error.data.ad) {
310+
delete error.data.ad
311+
}
312+
this.error.ts = utils.timestamp()
313+
ajax(
314+
pbaUrl,
315+
null,
316+
JSON.stringify(this.error),
317+
{
318+
contentType: 'application/json',
319+
method: 'POST',
320+
}
321+
)
322+
}
323+
}
324+
325+
export default sovrnAnalyticsAdapter;

0 commit comments

Comments
 (0)