prebid.js 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +1 -1
  2. package/browsers.json +13 -29
  3. package/karma.conf.maker.js +1 -1
  4. package/modules/admixerBidAdapter.js +2 -1
  5. package/modules/adnuntiusBidAdapter.js +2 -1
  6. package/modules/adplusBidAdapter.js +203 -0
  7. package/modules/adplusBidAdapter.md +39 -0
  8. package/modules/adyoulikeBidAdapter.js +7 -2
  9. package/modules/appnexusBidAdapter.js +19 -2
  10. package/modules/beachfrontBidAdapter.js +14 -17
  11. package/modules/craftBidAdapter.js +5 -3
  12. package/modules/dchain.js +149 -0
  13. package/modules/dchain.md +45 -0
  14. package/modules/emx_digitalBidAdapter.js +9 -1
  15. package/modules/freewheel-sspBidAdapter.js +6 -0
  16. package/modules/goldbachBidAdapter.js +1176 -0
  17. package/modules/goldbachBidAdapter.md +151 -0
  18. package/modules/gumgumBidAdapter.js +5 -1
  19. package/modules/intersectionRtdProvider.js +114 -0
  20. package/modules/invibesBidAdapter.js +15 -9
  21. package/modules/ipromBidAdapter.js +79 -0
  22. package/modules/limelightDigitalBidAdapter.js +2 -1
  23. package/modules/luponmediaBidAdapter.js +570 -0
  24. package/modules/missenaBidAdapter.js +89 -0
  25. package/modules/pubmaticBidAdapter.js +3 -3
  26. package/modules/relaidoBidAdapter.js +86 -65
  27. package/modules/richaudienceBidAdapter.js +1 -1
  28. package/modules/smaatoBidAdapter.js +4 -1
  29. package/modules/smartxBidAdapter.js +17 -1
  30. package/modules/tappxBidAdapter.js +3 -1
  31. package/modules/undertoneBidAdapter.js +8 -1
  32. package/modules/userId/index.js +27 -2
  33. package/modules/ventes.md +71 -0
  34. package/modules/ventesBidAdapter.js +104 -64
  35. package/modules/ventesBidAdapter.md +0 -1
  36. package/modules/visxBidAdapter.js +19 -2
  37. package/modules/visxBidAdapter.md +4 -6
  38. package/modules/yahoosspBidAdapter.md +1 -1
  39. package/modules/yieldoneBidAdapter.js +115 -11
  40. package/package.json +1 -1
  41. package/src/auction.js +3 -2
  42. package/src/targeting.js +2 -2
  43. package/src/utils.js +7 -0
  44. package/test/spec/integration/faker/googletag.js +6 -0
  45. package/test/spec/modules/adnuntiusBidAdapter_spec.js +18 -0
  46. package/test/spec/modules/adplusBidAdapter_spec.js +213 -0
  47. package/test/spec/modules/adyoulikeBidAdapter_spec.js +26 -0
  48. package/test/spec/modules/appnexusBidAdapter_spec.js +49 -1
  49. package/test/spec/modules/beachfrontBidAdapter_spec.js +65 -1
  50. package/test/spec/modules/dchain_spec.js +329 -0
  51. package/test/spec/modules/emx_digitalBidAdapter_spec.js +10 -0
  52. package/test/spec/modules/freewheel-sspBidAdapter_spec.js +19 -0
  53. package/test/spec/modules/goldbachBidAdapter_spec.js +1359 -0
  54. package/test/spec/modules/gumgumBidAdapter_spec.js +6 -0
  55. package/test/spec/modules/intersectionRtdProvider_spec.js +141 -0
  56. package/test/spec/modules/invibesBidAdapter_spec.js +29 -4
  57. package/test/spec/modules/ipromBidAdapter_spec.js +195 -0
  58. package/test/spec/modules/limelightDigitalBidAdapter_spec.js +10 -7
  59. package/test/spec/modules/luponmediaBidAdapter_spec.js +412 -0
  60. package/test/spec/modules/missenaBidAdapter_spec.js +134 -0
  61. package/test/spec/modules/pubmaticBidAdapter_spec.js +1 -1
  62. package/test/spec/modules/relaidoBidAdapter_spec.js +71 -63
  63. package/test/spec/modules/smaatoBidAdapter_spec.js +31 -0
  64. package/test/spec/modules/smartxBidAdapter_spec.js +9 -0
  65. package/test/spec/modules/tappxBidAdapter_spec.js +4 -0
  66. package/test/spec/modules/userId_spec.js +51 -0
  67. package/test/spec/modules/visxBidAdapter_spec.js +120 -4
  68. package/test/spec/modules/yieldoneBidAdapter_spec.js +299 -53
  69. package/test/spec/unit/core/targeting_spec.js +44 -0
@@ -0,0 +1,1176 @@
1
+ import { Renderer } from '../src/Renderer.js';
2
+ import {
3
+ isEmpty,
4
+ convertCamelToUnderscore,
5
+ isFn,
6
+ createTrackPixelHtml,
7
+ convertTypes,
8
+ deepClone,
9
+ fill,
10
+ getParameterByName,
11
+ getMaxValueFromArray,
12
+ getMinValueFromArray,
13
+ chunk,
14
+ isArray,
15
+ isArrayOfNums,
16
+ isNumber,
17
+ isStr,
18
+ isPlainObject,
19
+ logError,
20
+ logInfo,
21
+ logMessage,
22
+ deepAccess,
23
+ getBidRequest,
24
+ transformBidderParamKeywords
25
+ } from '../src/utils.js';
26
+ import { config } from '../src/config.js';
27
+ import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js';
28
+ import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js';
29
+ import { auctionManager } from '../src/auctionManager.js';
30
+ import find from 'core-js-pure/features/array/find.js';
31
+ import includes from 'core-js-pure/features/array/includes.js';
32
+ import { OUTSTREAM, INSTREAM } from '../src/video.js';
33
+
34
+ const BIDDER_CODE = 'goldbach';
35
+ const URL = 'https://ib.adnxs.com/ut/v3/prebid';
36
+ const PRICING_URL = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json';
37
+ const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid';
38
+ const VIDEO_TARGETING = ['id', 'minduration', 'maxduration',
39
+ 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset'];
40
+ const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api'];
41
+ const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language'];
42
+ const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately
43
+ const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout'];
44
+ const DEFAULT_PRICE_MAPPING = {
45
+ '0x0': 2.5,
46
+ '300x600': 5,
47
+ '800x250': 6,
48
+ '350x600': 6
49
+ };
50
+ let PRICE_MAPPING;
51
+ const VIDEO_MAPPING = {
52
+ playback_method: {
53
+ 'unknown': 0,
54
+ 'auto_play_sound_on': 1,
55
+ 'auto_play_sound_off': 2,
56
+ 'click_to_play': 3,
57
+ 'mouse_over': 4,
58
+ 'auto_play_sound_unknown': 5
59
+ },
60
+ context: {
61
+ 'unknown': 0,
62
+ 'pre_roll': 1,
63
+ 'mid_roll': 2,
64
+ 'post_roll': 3,
65
+ 'outstream': 4,
66
+ 'in-banner': 5
67
+ }
68
+ };
69
+ const NATIVE_MAPPING = {
70
+ body: 'description',
71
+ body2: 'desc2',
72
+ cta: 'ctatext',
73
+ image: {
74
+ serverName: 'main_image',
75
+ requiredParams: { required: true }
76
+ },
77
+ icon: {
78
+ serverName: 'icon',
79
+ requiredParams: { required: true }
80
+ },
81
+ sponsoredBy: 'sponsored_by',
82
+ privacyLink: 'privacy_link',
83
+ salePrice: 'saleprice',
84
+ displayUrl: 'displayurl'
85
+ };
86
+ const SOURCE = 'pbjs';
87
+ const MAX_IMPS_PER_REQUEST = 15;
88
+ const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/appnexus-mapping/mappings.json';
89
+ const SCRIPT_TAG_START = '<script';
90
+ const VIEWABILITY_URL_START = /\/\/cdn\.adnxs\.com\/v|\/\/cdn\.adnxs\-simple\.com\/v/;
91
+ const VIEWABILITY_FILE_NAME = 'trk.js';
92
+
93
+ export const spec = {
94
+ code: BIDDER_CODE,
95
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
96
+
97
+ /**
98
+ * Determines whether or not the given bid request is valid.
99
+ *
100
+ * @param {object} bid The bid to validate.
101
+ * @return boolean True if this is a valid bid, and false otherwise.
102
+ */
103
+ isBidRequestValid: function (bid) {
104
+ return !!(bid.params.placementId || (bid.params.member && bid.params.invCode));
105
+ },
106
+
107
+ /**
108
+ * Make a server request from the list of BidRequests.
109
+ *
110
+ * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
111
+ * @return ServerRequest Info describing the request to the server.
112
+ */
113
+ buildRequests: function (bidRequests, bidderRequest) {
114
+ let localBidRequests = [];
115
+ bidRequests.forEach(bid => {
116
+ if (Array.isArray(bid.params.placementId)) {
117
+ const ids = bid.params.placementId;
118
+ for (let i = 0; i < ids.length; i++) {
119
+ const newBid = Object.assign({}, bid, {params: {placementId: ids[i]}});
120
+ localBidRequests.push(newBid)
121
+ }
122
+ } else {
123
+ localBidRequests.push(bid);
124
+ }
125
+ });
126
+ const tags = localBidRequests.map(bidToTag);
127
+ const userObjBid = find(bidRequests, hasUserInfo);
128
+ let userObj = {};
129
+ if (config.getConfig('coppa') === true) {
130
+ userObj = { 'coppa': true };
131
+ }
132
+ if (userObjBid) {
133
+ Object.keys(userObjBid.params.user)
134
+ .filter(param => includes(USER_PARAMS, param))
135
+ .forEach((param) => {
136
+ let uparam = convertCamelToUnderscore(param);
137
+ if (param === 'segments' && isArray(userObjBid.params.user[param])) {
138
+ let segs = [];
139
+ userObjBid.params.user[param].forEach(val => {
140
+ if (isNumber(val)) {
141
+ segs.push({'id': val});
142
+ } else if (isPlainObject(val)) {
143
+ segs.push(val);
144
+ }
145
+ });
146
+ userObj[uparam] = segs;
147
+ } else if (param !== 'segments') {
148
+ userObj[uparam] = userObjBid.params.user[param];
149
+ }
150
+ });
151
+ }
152
+
153
+ const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo);
154
+ let appDeviceObj;
155
+ if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) {
156
+ appDeviceObj = {};
157
+ Object.keys(appDeviceObjBid.params.app)
158
+ .filter(param => includes(APP_DEVICE_PARAMS, param))
159
+ .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]);
160
+ }
161
+
162
+ const appIdObjBid = find(bidRequests, hasAppId);
163
+ let appIdObj;
164
+ if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) {
165
+ appIdObj = {
166
+ appid: appIdObjBid.params.app.id
167
+ };
168
+ }
169
+
170
+ let debugObj = {};
171
+ let debugObjParams = {};
172
+ const debugBidRequest = find(bidRequests, hasDebug);
173
+ if (debugBidRequest && debugBidRequest.debug) {
174
+ debugObj = debugBidRequest.debug;
175
+ }
176
+
177
+ if (debugObj && debugObj.enabled) {
178
+ Object.keys(debugObj)
179
+ .filter(param => includes(DEBUG_PARAMS, param))
180
+ .forEach(param => {
181
+ debugObjParams[param] = debugObj[param];
182
+ });
183
+ }
184
+
185
+ const memberIdBid = find(bidRequests, hasMemberId);
186
+ const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0;
187
+ const schain = bidRequests[0].schain;
188
+ const omidSupport = find(bidRequests, hasOmidSupport);
189
+
190
+ const payload = {
191
+ tags: [...tags],
192
+ user: userObj,
193
+ sdk: {
194
+ source: SOURCE,
195
+ version: '$prebid.version$'
196
+ },
197
+ schain: schain
198
+ };
199
+
200
+ if (omidSupport) {
201
+ payload['iab_support'] = {
202
+ omidpn: 'Appnexus',
203
+ omidpv: '$prebid.version$'
204
+ }
205
+ }
206
+
207
+ if (member > 0) {
208
+ payload.member_id = member;
209
+ }
210
+
211
+ if (appDeviceObjBid) {
212
+ payload.device = appDeviceObj
213
+ }
214
+ if (appIdObjBid) {
215
+ payload.app = appIdObj;
216
+ }
217
+
218
+ if (config.getConfig('adpod.brandCategoryExclusion')) {
219
+ payload.brand_category_uniqueness = true;
220
+ }
221
+
222
+ if (debugObjParams.enabled) {
223
+ payload.debug = debugObjParams;
224
+ logInfo('Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4));
225
+ }
226
+
227
+ if (bidderRequest && bidderRequest.gdprConsent) {
228
+ // note - objects for impbus use underscore instead of camelCase
229
+ payload.gdpr_consent = {
230
+ consent_string: bidderRequest.gdprConsent.consentString,
231
+ consent_required: bidderRequest.gdprConsent.gdprApplies
232
+ };
233
+
234
+ if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) {
235
+ let ac = bidderRequest.gdprConsent.addtlConsent;
236
+ // pull only the ids from the string (after the ~) and convert them to an array of ints
237
+ let acStr = ac.substring(ac.indexOf('~') + 1);
238
+ payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10));
239
+ }
240
+ }
241
+
242
+ if (bidderRequest && bidderRequest.uspConsent) {
243
+ payload.us_privacy = bidderRequest.uspConsent
244
+ }
245
+
246
+ if (bidderRequest && bidderRequest.refererInfo) {
247
+ let refererinfo = {
248
+ rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer),
249
+ rd_top: bidderRequest.refererInfo.reachedTop,
250
+ rd_ifs: bidderRequest.refererInfo.numIframes,
251
+ rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',')
252
+ }
253
+ payload.referrer_detection = refererinfo;
254
+ }
255
+
256
+ const hasAdPodBid = find(bidRequests, hasAdPod);
257
+ if (hasAdPodBid) {
258
+ bidRequests.filter(hasAdPod).forEach(adPodBid => {
259
+ const adPodTags = createAdPodRequest(tags, adPodBid);
260
+ // don't need the original adpod placement because it's in adPodTags
261
+ const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId);
262
+ payload.tags = [...nonPodTags, ...adPodTags];
263
+ });
264
+ }
265
+
266
+ if (bidRequests[0].userId) {
267
+ let eids = [];
268
+
269
+ addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null);
270
+ addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null);
271
+ addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null);
272
+ addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null);
273
+ addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID');
274
+ addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2');
275
+
276
+ if (eids.length) {
277
+ payload.eids = eids;
278
+ }
279
+ }
280
+
281
+ if (tags[0].publisher_id) {
282
+ payload.publisher_id = tags[0].publisher_id;
283
+ }
284
+
285
+ const request = formatRequest(payload, bidderRequest);
286
+ // add pricing endpoint
287
+ return [{method: 'GET', url: PRICING_URL, options: {withCredentials: false}}, request];
288
+ },
289
+
290
+ parseAndMapCpm: function(serverResponse) {
291
+ const responseBody = serverResponse.body;
292
+ if (Array.isArray(responseBody) && responseBody.length) {
293
+ let localData = {};
294
+ responseBody.forEach(cpmPerSize => {
295
+ Object.keys(cpmPerSize).forEach(size => {
296
+ let obj = {};
297
+ obj[size] = cpmPerSize[size];
298
+ localData = Object.assign({}, localData, obj)
299
+ })
300
+ })
301
+ PRICE_MAPPING = localData;
302
+ return null;
303
+ }
304
+
305
+ if (responseBody.version) {
306
+ const localPriceMapping = PRICE_MAPPING || DEFAULT_PRICE_MAPPING;
307
+ if (responseBody.tags && Array.isArray(responseBody.tags) && responseBody.tags.length) {
308
+ responseBody.tags.forEach((tag) => {
309
+ if (tag.ads && Array.isArray(tag.ads) && tag.ads.length) {
310
+ tag.ads.forEach(ad => {
311
+ if (ad.ad_type === 'banner') {
312
+ const size = `${ad.rtb.banner.width}x${ad.rtb.banner.height}`;
313
+ if (localPriceMapping[size]) {
314
+ ad.cpm = localPriceMapping[size];
315
+ } else {
316
+ ad.cpm = localPriceMapping['0x0'];
317
+ }
318
+ }
319
+ })
320
+ }
321
+ });
322
+ }
323
+ }
324
+ return responseBody;
325
+ },
326
+
327
+ /**
328
+ * Unpack the response from the server into a list of bids.
329
+ *
330
+ * @param {*} serverResponse A successful response from the server.
331
+ * @return {Bid[]} An array of bids which were nested inside the server.
332
+ */
333
+ interpretResponse: function (serverResponse, { bidderRequest }) {
334
+ serverResponse = this.parseAndMapCpm(serverResponse);
335
+ if (!serverResponse) return [];
336
+ const bids = [];
337
+ if (serverResponse.error) {
338
+ let errorMessage = `in response for ${bidderRequest.bidderCode} adapter : ${serverResponse.error}`;
339
+ logError(errorMessage);
340
+ return bids;
341
+ }
342
+
343
+ if (serverResponse.tags) {
344
+ serverResponse.tags.forEach(serverBid => {
345
+ const rtbBid = getRtbBid(serverBid);
346
+ if (rtbBid) {
347
+ if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) {
348
+ const bid = newBid(serverBid, rtbBid, bidderRequest);
349
+ bid.mediaType = parseMediaType(rtbBid);
350
+ bids.push(bid);
351
+ }
352
+ }
353
+ });
354
+ }
355
+
356
+ if (serverResponse.debug && serverResponse.debug.debug_info) {
357
+ let debugHeader = 'AppNexus Debug Auction for Prebid\n\n'
358
+ let debugText = debugHeader + serverResponse.debug.debug_info
359
+ debugText = debugText
360
+ .replace(/(<td>|<th>)/gm, '\t') // Tables
361
+ .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables
362
+ .replace(/^<br>/gm, '') // Remove leading <br>
363
+ .replace(/(<br>\n|<br>)/gm, '\n') // <br>
364
+ .replace(/<h1>(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1
365
+ .replace(/<h[2-6]>(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers
366
+ .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags
367
+ logMessage(debugText);
368
+ }
369
+
370
+ return bids;
371
+ },
372
+
373
+ /**
374
+ * @typedef {Object} mappingFileInfo
375
+ * @property {string} url mapping file json url
376
+ * @property {number} refreshInDays prebid stores mapping data in localstorage so you can return in how many days you want to update value stored in localstorage.
377
+ * @property {string} localStorageKey unique key to store your mapping json in localstorage
378
+ */
379
+
380
+ /**
381
+ * Returns mapping file info. This info will be used by bidderFactory to preload mapping file and store data in local storage
382
+ * @returns {mappingFileInfo}
383
+ */
384
+ getMappingFileInfo: function () {
385
+ return {
386
+ url: mappingFileUrl,
387
+ refreshInDays: 2
388
+ }
389
+ },
390
+
391
+ getUserSyncs: function (syncOptions, responses, gdprConsent) {
392
+ if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) {
393
+ return [{
394
+ type: 'iframe',
395
+ url: 'https://acdn.adnxs.com/dmp/async_usersync.html'
396
+ }];
397
+ }
398
+ },
399
+
400
+ transformBidParams: function (params, isOpenRtb) {
401
+ params = convertTypes({
402
+ 'member': 'string',
403
+ 'invCode': 'string',
404
+ 'placementId': 'number',
405
+ 'keywords': transformBidderParamKeywords,
406
+ 'publisherId': 'number'
407
+ }, params);
408
+
409
+ if (isOpenRtb) {
410
+ params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false;
411
+ if (params.usePaymentRule) { delete params.usePaymentRule; }
412
+
413
+ if (isPopulatedArray(params.keywords)) {
414
+ params.keywords.forEach(deleteValues);
415
+ }
416
+
417
+ Object.keys(params).forEach(paramKey => {
418
+ let convertedKey = convertCamelToUnderscore(paramKey);
419
+ if (convertedKey !== paramKey) {
420
+ params[convertedKey] = params[paramKey];
421
+ delete params[paramKey];
422
+ }
423
+ });
424
+ }
425
+
426
+ return params;
427
+ },
428
+
429
+ /**
430
+ * Add element selector to javascript tracker to improve native viewability
431
+ * @param {Bid} bid
432
+ */
433
+ onBidWon: function (bid) {
434
+ if (bid.native) {
435
+ reloadViewabilityScriptWithCorrectParameters(bid);
436
+ }
437
+ }
438
+ }
439
+
440
+ function isPopulatedArray(arr) {
441
+ return !!(isArray(arr) && arr.length > 0);
442
+ }
443
+
444
+ function deleteValues(keyPairObj) {
445
+ if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') {
446
+ delete keyPairObj.value;
447
+ }
448
+ }
449
+
450
+ function reloadViewabilityScriptWithCorrectParameters(bid) {
451
+ let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers);
452
+
453
+ if (viewJsPayload) {
454
+ let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode;
455
+
456
+ let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload)
457
+
458
+ let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams);
459
+
460
+ // find iframe containing script tag
461
+ let frameArray = document.getElementsByTagName('iframe');
462
+
463
+ // boolean var to modify only one script. That way if there are muliple scripts,
464
+ // they won't all point to the same creative.
465
+ let modifiedAScript = false;
466
+
467
+ // first, loop on all ifames
468
+ for (let i = 0; i < frameArray.length && !modifiedAScript; i++) {
469
+ let currentFrame = frameArray[i];
470
+ try {
471
+ // IE-compatible, see https://stackoverflow.com/a/3999191/2112089
472
+ let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document;
473
+
474
+ if (nestedDoc) {
475
+ // if the doc is present, we look for our jstracker
476
+ let scriptArray = nestedDoc.getElementsByTagName('script');
477
+ for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) {
478
+ let currentScript = scriptArray[j];
479
+ if (currentScript.getAttribute('data-src') == jsTrackerSrc) {
480
+ currentScript.setAttribute('src', newJsTrackerSrc);
481
+ currentScript.setAttribute('data-src', '');
482
+ if (currentScript.removeAttribute) {
483
+ currentScript.removeAttribute('data-src');
484
+ }
485
+ modifiedAScript = true;
486
+ }
487
+ }
488
+ }
489
+ } catch (exception) {
490
+ // trying to access a cross-domain iframe raises a SecurityError
491
+ // this is expected and ignored
492
+ if (!(exception instanceof DOMException && exception.name === 'SecurityError')) {
493
+ // all other cases are raised again to be treated by the calling function
494
+ throw exception;
495
+ }
496
+ }
497
+ }
498
+ }
499
+ }
500
+
501
+ function strIsAppnexusViewabilityScript(str) {
502
+ let regexMatchUrlStart = str.match(VIEWABILITY_URL_START);
503
+ let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1;
504
+
505
+ let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME);
506
+ let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1;
507
+
508
+ return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr;
509
+ }
510
+
511
+ function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) {
512
+ let viewJsPayload;
513
+ if (isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) {
514
+ viewJsPayload = jsTrackerArray;
515
+ } else if (isArray(jsTrackerArray)) {
516
+ for (let i = 0; i < jsTrackerArray.length; i++) {
517
+ let currentJsTracker = jsTrackerArray[i];
518
+ if (strIsAppnexusViewabilityScript(currentJsTracker)) {
519
+ viewJsPayload = currentJsTracker;
520
+ }
521
+ }
522
+ }
523
+ return viewJsPayload;
524
+ }
525
+
526
+ function getViewabilityScriptUrlFromPayload(viewJsPayload) {
527
+ // extracting the content of the src attribute
528
+ // -> substring between src=" and "
529
+ let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1
530
+ let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote);
531
+ let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote);
532
+ return jsTrackerSrc;
533
+ }
534
+
535
+ function hasPurpose1Consent(bidderRequest) {
536
+ let result = true;
537
+ if (bidderRequest && bidderRequest.gdprConsent) {
538
+ if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) {
539
+ result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true);
540
+ }
541
+ }
542
+ return result;
543
+ }
544
+
545
+ function formatRequest(payload, bidderRequest) {
546
+ let request = [];
547
+ let options = {
548
+ withCredentials: true
549
+ };
550
+
551
+ let endpointUrl = URL;
552
+
553
+ if (!hasPurpose1Consent(bidderRequest)) {
554
+ endpointUrl = URL_SIMPLE;
555
+ }
556
+
557
+ if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) {
558
+ options.customHeaders = {
559
+ 'X-Is-Test': 1
560
+ }
561
+ }
562
+
563
+ if (payload.tags.length > MAX_IMPS_PER_REQUEST) {
564
+ const clonedPayload = deepClone(payload);
565
+
566
+ chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => {
567
+ clonedPayload.tags = tags;
568
+ const payloadString = JSON.stringify(clonedPayload);
569
+ request.push({
570
+ method: 'POST',
571
+ url: endpointUrl,
572
+ data: payloadString,
573
+ bidderRequest,
574
+ options
575
+ });
576
+ });
577
+ } else {
578
+ const payloadString = JSON.stringify(payload);
579
+ request = {
580
+ method: 'POST',
581
+ url: endpointUrl,
582
+ data: payloadString,
583
+ bidderRequest,
584
+ options
585
+ };
586
+ }
587
+
588
+ return request;
589
+ }
590
+
591
+ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) {
592
+ const renderer = Renderer.install({
593
+ id: rtbBid.renderer_id,
594
+ url: rtbBid.renderer_url,
595
+ config: rendererOptions,
596
+ loaded: false,
597
+ adUnitCode
598
+ });
599
+
600
+ try {
601
+ renderer.setRender(outstreamRender);
602
+ } catch (err) {
603
+ logError('Prebid Error calling setRender on renderer', err);
604
+ }
605
+
606
+ renderer.setEventHandlers({
607
+ impression: () => logMessage('Outstream video impression event'),
608
+ loaded: () => logMessage('Outstream video loaded event'),
609
+ ended: () => {
610
+ logMessage('Outstream renderer video event');
611
+ document.querySelector(`#${adUnitCode}`).style.display = 'none';
612
+ }
613
+ });
614
+ return renderer;
615
+ }
616
+
617
+ /**
618
+ * Unpack the Server's Bid into a Prebid-compatible one.
619
+ * @param serverBid
620
+ * @param rtbBid
621
+ * @param bidderRequest
622
+ * @return Bid
623
+ */
624
+ function newBid(serverBid, rtbBid, bidderRequest) {
625
+ const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]);
626
+ const bid = {
627
+ requestId: serverBid.uuid,
628
+ cpm: rtbBid.cpm,
629
+ creativeId: rtbBid.creative_id,
630
+ dealId: rtbBid.deal_id,
631
+ currency: 'USD',
632
+ netRevenue: true,
633
+ ttl: 300,
634
+ adUnitCode: bidRequest.adUnitCode,
635
+ appnexus: {
636
+ buyerMemberId: rtbBid.buyer_member_id,
637
+ dealPriority: rtbBid.deal_priority,
638
+ dealCode: rtbBid.deal_code
639
+ }
640
+ };
641
+
642
+ // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance
643
+ if (rtbBid.adomain) {
644
+ bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] });
645
+ }
646
+
647
+ if (rtbBid.advertiser_id) {
648
+ bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id });
649
+ }
650
+
651
+ if (rtbBid.rtb.video) {
652
+ // shared video properties used for all 3 contexts
653
+ Object.assign(bid, {
654
+ width: rtbBid.rtb.video.player_width,
655
+ height: rtbBid.rtb.video.player_height,
656
+ vastImpUrl: rtbBid.notify_url,
657
+ ttl: 3600
658
+ });
659
+
660
+ const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context');
661
+ switch (videoContext) {
662
+ case ADPOD:
663
+ const primaryCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id);
664
+ bid.meta = Object.assign({}, bid.meta, { primaryCatId });
665
+ const dealTier = rtbBid.deal_priority;
666
+ bid.video = {
667
+ context: ADPOD,
668
+ durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000),
669
+ dealTier
670
+ };
671
+ bid.vastUrl = rtbBid.rtb.video.asset_url;
672
+ break;
673
+ case OUTSTREAM:
674
+ bid.adResponse = serverBid;
675
+ bid.adResponse.ad = bid.adResponse.ads[0];
676
+ bid.adResponse.ad.video = bid.adResponse.ad.rtb.video;
677
+ bid.vastXml = rtbBid.rtb.video.content;
678
+
679
+ if (rtbBid.renderer_url) {
680
+ const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid);
681
+ const rendererOptions = deepAccess(videoBid, 'renderer.options');
682
+ bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions);
683
+ }
684
+ break;
685
+ case INSTREAM:
686
+ bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url);
687
+ break;
688
+ }
689
+ } else if (rtbBid.rtb[NATIVE]) {
690
+ const nativeAd = rtbBid.rtb[NATIVE];
691
+
692
+ // setting up the jsTracker:
693
+ // we put it as a data-src attribute so that the tracker isn't called
694
+ // until we have the adId (see onBidWon)
695
+ let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src=');
696
+
697
+ let jsTrackers = nativeAd.javascript_trackers;
698
+
699
+ if (jsTrackers == undefined) {
700
+ jsTrackers = jsTrackerDisarmed;
701
+ } else if (isStr(jsTrackers)) {
702
+ jsTrackers = [jsTrackers, jsTrackerDisarmed];
703
+ } else {
704
+ jsTrackers.push(jsTrackerDisarmed);
705
+ }
706
+
707
+ bid[NATIVE] = {
708
+ title: nativeAd.title,
709
+ body: nativeAd.desc,
710
+ body2: nativeAd.desc2,
711
+ cta: nativeAd.ctatext,
712
+ rating: nativeAd.rating,
713
+ sponsoredBy: nativeAd.sponsored,
714
+ privacyLink: nativeAd.privacy_link,
715
+ address: nativeAd.address,
716
+ downloads: nativeAd.downloads,
717
+ likes: nativeAd.likes,
718
+ phone: nativeAd.phone,
719
+ price: nativeAd.price,
720
+ salePrice: nativeAd.saleprice,
721
+ clickUrl: nativeAd.link.url,
722
+ displayUrl: nativeAd.displayurl,
723
+ clickTrackers: nativeAd.link.click_trackers,
724
+ impressionTrackers: nativeAd.impression_trackers,
725
+ javascriptTrackers: jsTrackers
726
+ };
727
+ if (nativeAd.main_img) {
728
+ bid['native'].image = {
729
+ url: nativeAd.main_img.url,
730
+ height: nativeAd.main_img.height,
731
+ width: nativeAd.main_img.width,
732
+ };
733
+ }
734
+ if (nativeAd.icon) {
735
+ bid['native'].icon = {
736
+ url: nativeAd.icon.url,
737
+ height: nativeAd.icon.height,
738
+ width: nativeAd.icon.width,
739
+ };
740
+ }
741
+ } else {
742
+ Object.assign(bid, {
743
+ width: rtbBid.rtb.banner.width,
744
+ height: rtbBid.rtb.banner.height,
745
+ ad: rtbBid.rtb.banner.content
746
+ });
747
+ try {
748
+ if (rtbBid.rtb.trackers) {
749
+ const url = rtbBid.rtb.trackers[0].impression_urls[0];
750
+ const tracker = createTrackPixelHtml(url);
751
+ bid.ad += tracker;
752
+ }
753
+ } catch (error) {
754
+ logError('Error appending tracking pixel', error);
755
+ }
756
+ }
757
+
758
+ return bid;
759
+ }
760
+
761
+ function bidToTag(bid) {
762
+ const tag = {};
763
+ tag.sizes = transformSizes(bid.sizes);
764
+ tag.primary_size = tag.sizes[0];
765
+ tag.ad_types = [];
766
+ tag.uuid = bid.bidId;
767
+ if (bid.params.placementId) {
768
+ tag.id = parseInt(bid.params.placementId, 10);
769
+ } else {
770
+ tag.code = bid.params.invCode;
771
+ }
772
+ tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false;
773
+ tag.use_pmt_rule = bid.params.usePaymentRule || false
774
+ tag.prebid = true;
775
+ tag.disable_psa = true;
776
+ let bidFloor = getBidFloor(bid);
777
+ if (bidFloor) {
778
+ tag.reserve = bidFloor;
779
+ }
780
+ if (bid.params.position) {
781
+ tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0;
782
+ }
783
+ if (bid.params.trafficSourceCode) {
784
+ tag.traffic_source_code = bid.params.trafficSourceCode;
785
+ }
786
+ if (bid.params.privateSizes) {
787
+ tag.private_sizes = transformSizes(bid.params.privateSizes);
788
+ }
789
+ if (bid.params.supplyType) {
790
+ tag.supply_type = bid.params.supplyType;
791
+ }
792
+ if (bid.params.pubClick) {
793
+ tag.pubclick = bid.params.pubClick;
794
+ }
795
+ if (bid.params.extInvCode) {
796
+ tag.ext_inv_code = bid.params.extInvCode;
797
+ }
798
+ if (bid.params.publisherId) {
799
+ tag.publisher_id = parseInt(bid.params.publisherId, 10);
800
+ }
801
+ if (bid.params.externalImpId) {
802
+ tag.external_imp_id = bid.params.externalImpId;
803
+ }
804
+ if (!isEmpty(bid.params.keywords)) {
805
+ let keywords = transformBidderParamKeywords(bid.params.keywords);
806
+
807
+ if (keywords.length > 0) {
808
+ keywords.forEach(deleteValues);
809
+ }
810
+ tag.keywords = keywords;
811
+ }
812
+
813
+ let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot');
814
+ if (gpid) {
815
+ tag.gpid = gpid;
816
+ }
817
+
818
+ if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) {
819
+ tag.ad_types.push(NATIVE);
820
+ if (tag.sizes.length === 0) {
821
+ tag.sizes = transformSizes([1, 1]);
822
+ }
823
+
824
+ if (bid.nativeParams) {
825
+ const nativeRequest = buildNativeRequest(bid.nativeParams);
826
+ tag[NATIVE] = { layouts: [nativeRequest] };
827
+ }
828
+ }
829
+
830
+ const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`);
831
+ const context = deepAccess(bid, 'mediaTypes.video.context');
832
+
833
+ if (videoMediaType && context === 'adpod') {
834
+ tag.hb_source = 7;
835
+ } else {
836
+ tag.hb_source = 1;
837
+ }
838
+ if (bid.mediaType === VIDEO || videoMediaType) {
839
+ tag.ad_types.push(VIDEO);
840
+ }
841
+
842
+ // instream gets vastUrl, outstream gets vastXml
843
+ if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) {
844
+ tag.require_asset_url = true;
845
+ }
846
+
847
+ if (bid.params.video) {
848
+ tag.video = {};
849
+ // place any valid video params on the tag
850
+ Object.keys(bid.params.video)
851
+ .filter(param => includes(VIDEO_TARGETING, param))
852
+ .forEach(param => {
853
+ switch (param) {
854
+ case 'context':
855
+ case 'playback_method':
856
+ let type = bid.params.video[param];
857
+ type = (isArray(type)) ? type[0] : type;
858
+ tag.video[param] = VIDEO_MAPPING[param][type];
859
+ break;
860
+ // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks
861
+ case 'frameworks':
862
+ break;
863
+ default:
864
+ tag.video[param] = bid.params.video[param];
865
+ }
866
+ });
867
+
868
+ if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) {
869
+ tag['video_frameworks'] = bid.params.video.frameworks;
870
+ }
871
+ }
872
+
873
+ // use IAB ORTB values if the corresponding values weren't already set by bid.params.video
874
+ if (videoMediaType) {
875
+ tag.video = tag.video || {};
876
+ Object.keys(videoMediaType)
877
+ .filter(param => includes(VIDEO_RTB_TARGETING, param))
878
+ .forEach(param => {
879
+ switch (param) {
880
+ case 'minduration':
881
+ case 'maxduration':
882
+ if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param];
883
+ break;
884
+ case 'skip':
885
+ if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1);
886
+ break;
887
+ case 'skipafter':
888
+ if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param];
889
+ break;
890
+ case 'playbackmethod':
891
+ if (typeof tag.video['playback_method'] !== 'number') {
892
+ let type = videoMediaType[param];
893
+ type = (isArray(type)) ? type[0] : type;
894
+
895
+ // we only support iab's options 1-4 at this time.
896
+ if (type >= 1 && type <= 4) {
897
+ tag.video['playback_method'] = type;
898
+ }
899
+ }
900
+ break;
901
+ case 'api':
902
+ if (!tag['video_frameworks'] && isArray(videoMediaType[param])) {
903
+ // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values)
904
+ let apiTmp = videoMediaType[param].map(val => {
905
+ let v = (val === 4) ? 5 : (val === 5) ? 4 : val;
906
+
907
+ if (v >= 1 && v <= 5) {
908
+ return v;
909
+ }
910
+ }).filter(v => v);
911
+ tag['video_frameworks'] = apiTmp;
912
+ }
913
+ break;
914
+ }
915
+ });
916
+ }
917
+
918
+ if (bid.renderer) {
919
+ tag.video = Object.assign({}, tag.video, { custom_renderer_present: true });
920
+ }
921
+
922
+ if (bid.params.frameworks && isArray(bid.params.frameworks)) {
923
+ tag['banner_frameworks'] = bid.params.frameworks;
924
+ }
925
+
926
+ let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId);
927
+ if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) {
928
+ tag.ad_types.push(BANNER);
929
+ }
930
+
931
+ if (tag.ad_types.length === 0) {
932
+ delete tag.ad_types;
933
+ }
934
+
935
+ return tag;
936
+ }
937
+
938
+ /* Turn bid request sizes into ut-compatible format */
939
+ function transformSizes(requestSizes) {
940
+ let sizes = [];
941
+ let sizeObj = {};
942
+
943
+ if (isArray(requestSizes) && requestSizes.length === 2 &&
944
+ !isArray(requestSizes[0])) {
945
+ sizeObj.width = parseInt(requestSizes[0], 10);
946
+ sizeObj.height = parseInt(requestSizes[1], 10);
947
+ sizes.push(sizeObj);
948
+ } else if (typeof requestSizes === 'object') {
949
+ for (let i = 0; i < requestSizes.length; i++) {
950
+ let size = requestSizes[i];
951
+ sizeObj = {};
952
+ sizeObj.width = parseInt(size[0], 10);
953
+ sizeObj.height = parseInt(size[1], 10);
954
+ sizes.push(sizeObj);
955
+ }
956
+ }
957
+
958
+ return sizes;
959
+ }
960
+
961
+ function hasUserInfo(bid) {
962
+ return !!bid.params.user;
963
+ }
964
+
965
+ function hasMemberId(bid) {
966
+ return !!parseInt(bid.params.member, 10);
967
+ }
968
+
969
+ function hasAppDeviceInfo(bid) {
970
+ if (bid.params) {
971
+ return !!bid.params.app
972
+ }
973
+ }
974
+
975
+ function hasAppId(bid) {
976
+ if (bid.params && bid.params.app) {
977
+ return !!bid.params.app.id
978
+ }
979
+ return !!bid.params.app
980
+ }
981
+
982
+ function hasDebug(bid) {
983
+ return !!bid.debug
984
+ }
985
+
986
+ function hasAdPod(bid) {
987
+ return (
988
+ bid.mediaTypes &&
989
+ bid.mediaTypes.video &&
990
+ bid.mediaTypes.video.context === ADPOD
991
+ );
992
+ }
993
+
994
+ function hasOmidSupport(bid) {
995
+ let hasOmid = false;
996
+ const bidderParams = bid.params;
997
+ const videoParams = bid.params.video;
998
+ if (bidderParams.frameworks && isArray(bidderParams.frameworks)) {
999
+ hasOmid = includes(bid.params.frameworks, 6);
1000
+ }
1001
+ if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) {
1002
+ hasOmid = includes(bid.params.video.frameworks, 6);
1003
+ }
1004
+ return hasOmid;
1005
+ }
1006
+
1007
+ /**
1008
+ * Expand an adpod placement into a set of request objects according to the
1009
+ * total adpod duration and the range of duration seconds. Sets minduration/
1010
+ * maxduration video property according to requireExactDuration configuration
1011
+ */
1012
+ function createAdPodRequest(tags, adPodBid) {
1013
+ const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video;
1014
+
1015
+ const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video);
1016
+ const maxDuration = getMaxValueFromArray(durationRangeSec);
1017
+
1018
+ const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId);
1019
+ let request = fill(...tagToDuplicate, numberOfPlacements);
1020
+
1021
+ if (requireExactDuration) {
1022
+ const divider = Math.ceil(numberOfPlacements / durationRangeSec.length);
1023
+ const chunked = chunk(request, divider);
1024
+
1025
+ // each configured duration is set as min/maxduration for a subset of requests
1026
+ durationRangeSec.forEach((duration, index) => {
1027
+ chunked[index].map(tag => {
1028
+ setVideoProperty(tag, 'minduration', duration);
1029
+ setVideoProperty(tag, 'maxduration', duration);
1030
+ });
1031
+ });
1032
+ } else {
1033
+ // all maxdurations should be the same
1034
+ request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration));
1035
+ }
1036
+
1037
+ return request;
1038
+ }
1039
+
1040
+ function getAdPodPlacementNumber(videoParams) {
1041
+ const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams;
1042
+ const minAllowedDuration = getMinValueFromArray(durationRangeSec);
1043
+ const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration);
1044
+
1045
+ return requireExactDuration
1046
+ ? Math.max(numberOfPlacements, durationRangeSec.length)
1047
+ : numberOfPlacements;
1048
+ }
1049
+
1050
+ function setVideoProperty(tag, key, value) {
1051
+ if (isEmpty(tag.video)) { tag.video = {}; }
1052
+ tag.video[key] = value;
1053
+ }
1054
+
1055
+ function getRtbBid(tag) {
1056
+ return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb);
1057
+ }
1058
+
1059
+ function buildNativeRequest(params) {
1060
+ const request = {};
1061
+
1062
+ // map standard prebid native asset identifier to /ut parameters
1063
+ // e.g., tag specifies `body` but /ut only knows `description`.
1064
+ // mapping may be in form {tag: '<server name>'} or
1065
+ // {tag: {serverName: '<server name>', requiredParams: {...}}}
1066
+ Object.keys(params).forEach(key => {
1067
+ // check if one of the <server name> forms is used, otherwise
1068
+ // a mapping wasn't specified so pass the key straight through
1069
+ const requestKey =
1070
+ (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) ||
1071
+ NATIVE_MAPPING[key] ||
1072
+ key;
1073
+
1074
+ // required params are always passed on request
1075
+ const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams;
1076
+ request[requestKey] = Object.assign({}, requiredParams, params[key]);
1077
+
1078
+ // convert the sizes of image/icon assets to proper format (if needed)
1079
+ const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName);
1080
+ if (isImageAsset && request[requestKey].sizes) {
1081
+ let sizes = request[requestKey].sizes;
1082
+ if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) {
1083
+ request[requestKey].sizes = transformSizes(request[requestKey].sizes);
1084
+ }
1085
+ }
1086
+
1087
+ if (requestKey === NATIVE_MAPPING.privacyLink) {
1088
+ request.privacy_supported = true;
1089
+ }
1090
+ });
1091
+
1092
+ return request;
1093
+ }
1094
+
1095
+ /**
1096
+ * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative.
1097
+ * @param {string} elementId element id
1098
+ */
1099
+ function hidedfpContainer(elementId) {
1100
+ var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']");
1101
+ if (el[0]) {
1102
+ el[0].style.setProperty('display', 'none');
1103
+ }
1104
+ }
1105
+
1106
+ function hideSASIframe(elementId) {
1107
+ try {
1108
+ // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server.
1109
+ const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']");
1110
+ if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') {
1111
+ el[0].nextSibling.style.setProperty('display', 'none');
1112
+ }
1113
+ } catch (e) {
1114
+ // element not found!
1115
+ }
1116
+ }
1117
+
1118
+ function outstreamRender(bid) {
1119
+ hidedfpContainer(bid.adUnitCode);
1120
+ hideSASIframe(bid.adUnitCode);
1121
+ // push to render queue because ANOutstreamVideo may not be loaded yet
1122
+ bid.renderer.push(() => {
1123
+ window.ANOutstreamVideo.renderAd({
1124
+ tagId: bid.adResponse.tag_id,
1125
+ sizes: [bid.getSize().split('x')],
1126
+ targetId: bid.adUnitCode, // target div id to render video
1127
+ uuid: bid.adResponse.uuid,
1128
+ adResponse: bid.adResponse,
1129
+ rendererOptions: bid.renderer.getConfig()
1130
+ }, handleOutstreamRendererEvents.bind(null, bid));
1131
+ });
1132
+ }
1133
+
1134
+ function handleOutstreamRendererEvents(bid, id, eventName) {
1135
+ bid.renderer.handleVideoEvent({ id, eventName });
1136
+ }
1137
+
1138
+ function parseMediaType(rtbBid) {
1139
+ const adType = rtbBid.ad_type;
1140
+ if (adType === VIDEO) {
1141
+ return VIDEO;
1142
+ } else if (adType === NATIVE) {
1143
+ return NATIVE;
1144
+ } else {
1145
+ return BANNER;
1146
+ }
1147
+ }
1148
+
1149
+ function addUserId(eids, id, source, rti) {
1150
+ if (id) {
1151
+ if (rti) {
1152
+ eids.push({ source, id, rti_partner: rti });
1153
+ } else {
1154
+ eids.push({ source, id });
1155
+ }
1156
+ }
1157
+ return eids;
1158
+ }
1159
+
1160
+ function getBidFloor(bid) {
1161
+ if (!isFn(bid.getFloor)) {
1162
+ return (bid.params.reserve) ? bid.params.reserve : null;
1163
+ }
1164
+
1165
+ let floor = bid.getFloor({
1166
+ currency: 'USD',
1167
+ mediaType: '*',
1168
+ size: '*'
1169
+ });
1170
+ if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
1171
+ return floor.floor;
1172
+ }
1173
+ return null;
1174
+ }
1175
+
1176
+ registerBidder(spec);