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.
- package/README.md +1 -1
- package/browsers.json +13 -29
- package/karma.conf.maker.js +1 -1
- package/modules/admixerBidAdapter.js +2 -1
- package/modules/adnuntiusBidAdapter.js +2 -1
- package/modules/adplusBidAdapter.js +203 -0
- package/modules/adplusBidAdapter.md +39 -0
- package/modules/adyoulikeBidAdapter.js +7 -2
- package/modules/appnexusBidAdapter.js +19 -2
- package/modules/beachfrontBidAdapter.js +14 -17
- package/modules/craftBidAdapter.js +5 -3
- package/modules/dchain.js +149 -0
- package/modules/dchain.md +45 -0
- package/modules/emx_digitalBidAdapter.js +9 -1
- package/modules/freewheel-sspBidAdapter.js +6 -0
- package/modules/goldbachBidAdapter.js +1176 -0
- package/modules/goldbachBidAdapter.md +151 -0
- package/modules/gumgumBidAdapter.js +5 -1
- package/modules/intersectionRtdProvider.js +114 -0
- package/modules/invibesBidAdapter.js +15 -9
- package/modules/ipromBidAdapter.js +79 -0
- package/modules/limelightDigitalBidAdapter.js +2 -1
- package/modules/luponmediaBidAdapter.js +570 -0
- package/modules/missenaBidAdapter.js +89 -0
- package/modules/pubmaticBidAdapter.js +3 -3
- package/modules/relaidoBidAdapter.js +86 -65
- package/modules/richaudienceBidAdapter.js +1 -1
- package/modules/smaatoBidAdapter.js +4 -1
- package/modules/smartxBidAdapter.js +17 -1
- package/modules/tappxBidAdapter.js +3 -1
- package/modules/undertoneBidAdapter.js +8 -1
- package/modules/userId/index.js +27 -2
- package/modules/ventes.md +71 -0
- package/modules/ventesBidAdapter.js +104 -64
- package/modules/ventesBidAdapter.md +0 -1
- package/modules/visxBidAdapter.js +19 -2
- package/modules/visxBidAdapter.md +4 -6
- package/modules/yahoosspBidAdapter.md +1 -1
- package/modules/yieldoneBidAdapter.js +115 -11
- package/package.json +1 -1
- package/src/auction.js +3 -2
- package/src/targeting.js +2 -2
- package/src/utils.js +7 -0
- package/test/spec/integration/faker/googletag.js +6 -0
- package/test/spec/modules/adnuntiusBidAdapter_spec.js +18 -0
- package/test/spec/modules/adplusBidAdapter_spec.js +213 -0
- package/test/spec/modules/adyoulikeBidAdapter_spec.js +26 -0
- package/test/spec/modules/appnexusBidAdapter_spec.js +49 -1
- package/test/spec/modules/beachfrontBidAdapter_spec.js +65 -1
- package/test/spec/modules/dchain_spec.js +329 -0
- package/test/spec/modules/emx_digitalBidAdapter_spec.js +10 -0
- package/test/spec/modules/freewheel-sspBidAdapter_spec.js +19 -0
- package/test/spec/modules/goldbachBidAdapter_spec.js +1359 -0
- package/test/spec/modules/gumgumBidAdapter_spec.js +6 -0
- package/test/spec/modules/intersectionRtdProvider_spec.js +141 -0
- package/test/spec/modules/invibesBidAdapter_spec.js +29 -4
- package/test/spec/modules/ipromBidAdapter_spec.js +195 -0
- package/test/spec/modules/limelightDigitalBidAdapter_spec.js +10 -7
- package/test/spec/modules/luponmediaBidAdapter_spec.js +412 -0
- package/test/spec/modules/missenaBidAdapter_spec.js +134 -0
- package/test/spec/modules/pubmaticBidAdapter_spec.js +1 -1
- package/test/spec/modules/relaidoBidAdapter_spec.js +71 -63
- package/test/spec/modules/smaatoBidAdapter_spec.js +31 -0
- package/test/spec/modules/smartxBidAdapter_spec.js +9 -0
- package/test/spec/modules/tappxBidAdapter_spec.js +4 -0
- package/test/spec/modules/userId_spec.js +51 -0
- package/test/spec/modules/visxBidAdapter_spec.js +120 -4
- package/test/spec/modules/yieldoneBidAdapter_spec.js +299 -53
- 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);
|