forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathliveyieldAnalyticsAdapter.js
454 lines (422 loc) · 14.1 KB
/
liveyieldAnalyticsAdapter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
import { logError } from '../src/utils.js';
import adapter from '../src/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
const {
EVENTS: { BID_REQUESTED, BID_TIMEOUT, BID_RESPONSE, BID_WON }
} = CONSTANTS;
const prebidVersion = '$prebid.version$';
const adapterConfig = {
/** Name of the `rta` function, override only when instructed. */
rtaFunctionName: 'rta',
/** This is optional but highly recommended. The value returned by the
* function will be used as ad impression ad unit attribute value.
*
* As such if you have placement (10293845) or ad unit codes
* (div-gpt-ad-124984-0) but you want these to be translated to meaningful
* values like 'SIDEBAR-AD-01-MOBILE' then this function shall express this
* mapping.
*/
getAdUnitName: function(placementOrAdUnitCode) {
return placementOrAdUnitCode;
},
/**
* Function used to extract placement/adUnitCode (depending on prebid
* version).
*
* The extracted value will be passed to the `getAdUnitName()` for mapping into
* human friendly value.
*/
getPlacementOrAdUnitCode: function(bid, version) {
return version[0] === '0' ? bid.placementCode : bid.adUnitCode;
},
/**
* Optional reference to Google Publisher Tag (gpt)
*/
googlePublisherTag: false,
/**
* Do not override unless instructed. Useful for testing. Allows to redefined
* the event that triggers the ad impression event.
*/
wireGooglePublisherTag: function(gpt, cb) {
gpt.pubads().addEventListener('slotRenderEnded', function(event) {
cb(event.slot);
});
},
/**
* Map which keeps BID_WON events. Keyed by adId property.
*/
prebidWinnersCache: {},
/**
* Map which keeps all BID_RESPONSE events. Keyed by adId property.
*/
prebidBidResponsesCache: {},
/**
* Decides if the GPT slot contains prebid ad impression or not.
*
* When BID_WON event is emitted adid is added to prebidWinnersCache,
* then we check if prebidWinnersCache contains slot.hb_adid.
*
* This function is optional and used only when googlePublisherTag is provided.
*
* Default implementation uses slot's `hb_adid` targeting parameter.
*
* @param slot the gpt slot
*/
isPrebidAdImpression: function(slot) {
const hbAdIdTargeting = slot.getTargeting('hb_adid');
if (hbAdIdTargeting.length > 0) {
const hbAdId = hbAdIdTargeting[0];
return typeof this.prebidWinnersCache[hbAdId] !== 'undefined';
}
return false;
},
/**
* If isPrebidAdImpression decides that slot contain prebid ad impression,
* this function should return prebids highest ad impression partner for that
* slot.
*
* Default implementation uses slot's `hb_adid` targeting value to find
* highest bid response and when present then returns `bidder`.
*
* @param instanceConfig merged analytics adapter instance configuration
* @param slot the gpt slot for which the name of the highest bidder shall be
* returned
* @param version the version of the prebid.js library
*/
getHighestPrebidAdImpressionPartner: function(instanceConfig, slot, version) {
const bid = getHighestPrebidBidResponseBySlotTargeting(
instanceConfig,
slot,
version
);
// this is bid response event has `bidder` while bid won has bidderCode property
return bid ? bid.bidderCode || bid.bidder : null;
},
/**
* If isPrebidAdImpression decides that slot contain prebid ad impression,
* this function should return prebids highest ad impression value for that
* slot.
*
* Default implementation uses slot's `hb_adid` targeting value to find
* highest bid response and when present then returns `cpm`.
*
* @param instanceConfig merged analytics adapter instance configuration
* @param slot the gpt slot for which the highest ad impression value shall be
* returned
* @param version the version of the prebid.js library
*/
getHighestPrebidAdImpressionValue: function(instanceConfig, slot, version) {
const bid = getHighestPrebidBidResponseBySlotTargeting(
instanceConfig,
slot,
version
);
return bid ? bid.cpm : null;
},
/**
* This function should return proper ad unit name for slot given as a
* parameter. Unit names returned by this function should be meaningful, for
* example 'FOO_728x90_TOP'. The values returned shall be inline with
* `getAdUnitName`.
*
* Required when googlePublisherTag is defined.
*
* @param slot the gpt slot to translate into friendly name
* @param version the version of the prebid.js library
*/
getAdUnitNameByGooglePublisherTagSlot: (slot, version) => {
throw 'Required when googlePublisherTag is defined.';
},
/**
* Function used to prepare and return parameters provided to rta.
* More information will be in docs given by LiveYield team.
*
* When googlePublisherTag is not provided, second parameter(slot) will always
* equal null.
*
* @param resolution the original ad impression details
* @param slot gpt slot, will be empty in pure Prebid.js-case (when
* googlePublisherTag is not provided)
* @param hbPartner the name of the highest bidding partner
* @param hbValue the value of the highest bid
* @param version version of the prebid.js library
*/
postProcessResolution: (resolution, slot, hbPartner, hbValue, version) => {
return resolution;
}
};
const cpmToMicroUSD = v => (isNaN(v) ? 0 : Math.round(v * 1000));
const getHighestPrebidBidResponseBySlotTargeting = function(
instanceConfig,
slot,
version
) {
const hbAdIdTargeting = slot.getTargeting('hb_adid');
if (hbAdIdTargeting.length > 0) {
const hbAdId = hbAdIdTargeting[0];
return (
instanceConfig.prebidWinnersCache[hbAdId] ||
instanceConfig.prebidBidResponsesCache[hbAdId]
);
}
return null;
};
const liveyield = Object.assign(adapter({ analyticsType: 'bundle' }), {
track({ eventType, args }) {
switch (eventType) {
case BID_REQUESTED:
args.bids.forEach(function(b) {
try {
window[liveyield.instanceConfig.rtaFunctionName](
'bidRequested',
liveyield.instanceConfig.getAdUnitName(
liveyield.instanceConfig.getPlacementOrAdUnitCode(
b,
prebidVersion
)
),
args.bidderCode
);
} catch (e) {
logError(e);
}
});
break;
case BID_RESPONSE:
liveyield.instanceConfig.prebidBidResponsesCache[args.adId] = args;
var cpm = args.statusMessage === 'Bid available' ? args.cpm : null;
try {
window[liveyield.instanceConfig.rtaFunctionName](
'addBid',
liveyield.instanceConfig.getAdUnitName(
liveyield.instanceConfig.getPlacementOrAdUnitCode(
args,
prebidVersion
)
),
args.bidder || 'unknown',
cpmToMicroUSD(cpm),
typeof args.bidder === 'undefined',
args.statusMessage !== 'Bid available'
);
} catch (e) {
logError(e);
}
break;
case BID_TIMEOUT:
window[liveyield.instanceConfig.rtaFunctionName](
'biddersTimeout',
args
);
break;
case BID_WON:
liveyield.instanceConfig.prebidWinnersCache[args.adId] = args;
if (liveyield.instanceConfig.googlePublisherTag) {
break;
}
try {
const ad = liveyield.instanceConfig.getAdUnitName(
liveyield.instanceConfig.getPlacementOrAdUnitCode(
args,
prebidVersion
)
);
if (!ad) {
logError(
'Cannot find ad by unit name: ' +
liveyield.instanceConfig.getAdUnitName(
liveyield.instanceConfig.getPlacementOrAdUnitCode(
args,
prebidVersion
)
)
);
break;
}
if (!args.bidderCode || !args.cpm) {
logError('Bidder code or cpm is not valid');
break;
}
const resolution = { targetings: [] };
resolution.prebidWon = true;
resolution.prebidPartner = args.bidderCode;
resolution.prebidValue = cpmToMicroUSD(parseFloat(args.cpm));
const resolutionToUse = liveyield.instanceConfig.postProcessResolution(
resolution,
null,
resolution.prebidPartner,
resolution.prebidValue,
prebidVersion
);
window[liveyield.instanceConfig.rtaFunctionName](
'resolveSlot',
liveyield.instanceConfig.getAdUnitName(
liveyield.instanceConfig.getPlacementOrAdUnitCode(
args,
prebidVersion
)
),
resolutionToUse
);
} catch (e) {
logError(e);
}
break;
}
}
});
liveyield.originEnableAnalytics = liveyield.enableAnalytics;
/**
* Minimal valid config:
*
* ```
* {
* provider: 'liveyield',
* options: {
* // will be provided by the LiveYield team
* customerId: 'UUID',
* // will be provided by the LiveYield team,
* customerName: 'Customer Name',
* // do NOT use window.location.host, use constant value
* customerSite: 'Fixed Site Name',
* // this is used to be inline with GA 'sessionizer' which closes the session on midnight (EST-time).
* sessionTimezoneOffset: '-300'
* }
* }
* ```
*/
liveyield.enableAnalytics = function(config) {
if (!config || !config.provider || config.provider !== 'liveyield') {
logError('expected config.provider to equal liveyield');
return;
}
if (!config.options) {
logError('options must be defined');
return;
}
if (!config.options.customerId) {
logError('options.customerId is required');
return;
}
if (!config.options.customerName) {
logError('options.customerName is required');
return;
}
if (!config.options.customerSite) {
logError('options.customerSite is required');
return;
}
if (!config.options.sessionTimezoneOffset) {
logError('options.sessionTimezoneOffset is required');
return;
}
liveyield.instanceConfig = Object.assign(
{ prebidWinnersCache: {}, prebidBidResponsesCache: {} },
adapterConfig,
config.options
);
if (typeof window[liveyield.instanceConfig.rtaFunctionName] !== 'function') {
logError(
`Function ${liveyield.instanceConfig.rtaFunctionName} is not defined.` +
`Make sure that LiveYield snippet in included before the Prebid Analytics configuration.`
);
return;
}
if (liveyield.instanceConfig.googlePublisherTag) {
liveyield.instanceConfig.wireGooglePublisherTag(
liveyield.instanceConfig.googlePublisherTag,
onSlotRenderEnded(liveyield.instanceConfig)
);
}
const additionalParams = {
customerTimezone: config.options.customerTimezone,
contentId: config.options.contentId,
contentPart: config.options.contentPart,
contentAuthor: config.options.contentAuthor,
contentTitle: config.options.contentTitle,
contentCategory: config.options.contentCategory,
contentLayout: config.options.contentLayout,
contentVariants: config.options.contentVariants,
contentTimezone: config.options.contentTimezone,
cstringDim1: config.options.cstringDim1,
cstringDim2: config.options.cstringDim2,
cintDim1: config.options.cintDim1,
cintDim2: config.options.cintDim2,
cintArrayDim1: config.options.cintArrayDim1,
cintArrayDim2: config.options.cintArrayDim2,
cuniqueStringMet1: config.options.cuniqueStringMet1,
cuniqueStringMet2: config.options.cuniqueStringMet2,
cavgIntMet1: config.options.cavgIntMet1,
cavgIntMet2: config.options.cavgIntMet2,
csumIntMet1: config.options.csumIntMet1,
csumIntMet2: config.options.csumIntMet2
};
Object.keys(additionalParams).forEach(
key => additionalParams[key] == null && delete additionalParams[key]
);
window[liveyield.instanceConfig.rtaFunctionName](
'create',
config.options.customerId,
config.options.customerName,
config.options.customerSite,
config.options.sessionTimezoneOffset,
additionalParams
);
liveyield.originEnableAnalytics(config);
};
const onSlotRenderEnded = function(instanceConfig) {
const addDfpDetails = (resolution, slot) => {
const responseInformation = slot.getResponseInformation();
if (responseInformation) {
resolution.dfpAdvertiserId = responseInformation.advertiserId;
resolution.dfpLineItemId = responseInformation.sourceAgnosticLineItemId;
resolution.dfpCreativeId = responseInformation.creativeId;
}
};
const addPrebidDetails = (resolution, slot) => {
if (instanceConfig.isPrebidAdImpression(slot)) {
resolution.prebidWon = true;
}
const highestPrebidAdImpPartner = instanceConfig.getHighestPrebidAdImpressionPartner(
instanceConfig,
slot,
prebidVersion
);
const highestPrebidAdImpValue = instanceConfig.getHighestPrebidAdImpressionValue(
instanceConfig,
slot,
prebidVersion
);
if (highestPrebidAdImpPartner) {
resolution.prebidPartner = highestPrebidAdImpPartner;
}
if (highestPrebidAdImpValue) {
resolution.prebidValue = cpmToMicroUSD(
parseFloat(highestPrebidAdImpValue)
);
}
};
return slot => {
const resolution = { targetings: [] };
addDfpDetails(resolution, slot);
addPrebidDetails(resolution, slot);
const resolutionToUse = instanceConfig.postProcessResolution(
resolution,
slot,
resolution.highestPrebidAdImpPartner,
resolution.highestPrebidAdImpValue,
prebidVersion
);
window[instanceConfig.rtaFunctionName](
'resolveSlot',
instanceConfig.getAdUnitNameByGooglePublisherTagSlot(slot, prebidVersion),
resolutionToUse
);
};
};
adapterManager.registerAnalyticsAdapter({
adapter: liveyield,
code: 'liveyield'
});
export default liveyield;