prebid.js 6.2.0 → 6.3.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 (60) hide show
  1. package/gulpfile.js +0 -8
  2. package/integrationExamples/gpt/weboramaRtdProvider_example.html +23 -14
  3. package/modules/33acrossBidAdapter.js +189 -102
  4. package/modules/adagioBidAdapter.js +1 -1
  5. package/modules/addefendBidAdapter.js +1 -0
  6. package/modules/admanBidAdapter.js +1 -0
  7. package/modules/admixerBidAdapter.js +1 -1
  8. package/modules/adnuntiusBidAdapter.js +3 -1
  9. package/modules/adxcgBidAdapter.js +311 -359
  10. package/modules/adxcgBidAdapter.md +22 -21
  11. package/modules/appnexusBidAdapter.js +0 -1
  12. package/modules/beopBidAdapter.js +5 -3
  13. package/modules/colossussspBidAdapter.js +7 -0
  14. package/modules/criteoBidAdapter.js +1 -1
  15. package/modules/cwireBidAdapter.js +3 -0
  16. package/modules/dspxBidAdapter.js +1 -1
  17. package/modules/futureads.md +48 -0
  18. package/modules/glimpseBidAdapter.js +16 -3
  19. package/modules/gumgumBidAdapter.js +2 -2
  20. package/modules/kinessoIdSystem.js +1 -1
  21. package/modules/lotamePanoramaIdSystem.js +80 -8
  22. package/modules/mediasquareBidAdapter.js +1 -9
  23. package/modules/nextMillenniumBidAdapter.js +29 -1
  24. package/modules/prebidServerBidAdapter/index.js +16 -12
  25. package/modules/richaudienceBidAdapter.js +1 -2
  26. package/modules/rtdModule/index.js +48 -18
  27. package/modules/rubiconBidAdapter.js +10 -8
  28. package/modules/sharedIdSystem.js +27 -1
  29. package/modules/targetVideoBidAdapter.js +187 -0
  30. package/modules/targetVideoBidAdapter.md +34 -0
  31. package/modules/vidoomyBidAdapter.js +16 -10
  32. package/modules/weboramaRtdProvider.js +288 -73
  33. package/modules/weboramaRtdProvider.md +27 -10
  34. package/modules/yahoosspBidAdapter.js +5 -1
  35. package/modules/zetaSspBidAdapter.md +33 -1
  36. package/modules/zeta_global_sspBidAdapter.js +22 -1
  37. package/package.json +1 -1
  38. package/plugins/pbjsGlobals.js +28 -1
  39. package/src/prebid.js +1 -2
  40. package/src/targeting.js +22 -1
  41. package/src/utils.js +34 -7
  42. package/test/spec/modules/33acrossBidAdapter_spec.js +300 -78
  43. package/test/spec/modules/adnuntiusBidAdapter_spec.js +17 -0
  44. package/test/spec/modules/adxcgBidAdapter_spec.js +820 -571
  45. package/test/spec/modules/colossussspBidAdapter_spec.js +9 -0
  46. package/test/spec/modules/cwireBidAdapter_spec.js +10 -8
  47. package/test/spec/modules/lotamePanoramaIdSystem_spec.js +227 -0
  48. package/test/spec/modules/mediasquareBidAdapter_spec.js +4 -4
  49. package/test/spec/modules/nextMillenniumBidAdapter_spec.js +18 -0
  50. package/test/spec/modules/prebidServerBidAdapter_spec.js +43 -0
  51. package/test/spec/modules/realTimeDataModule_spec.js +135 -49
  52. package/test/spec/modules/richaudienceBidAdapter_spec.js +2 -2
  53. package/test/spec/modules/rubiconBidAdapter_spec.js +17 -0
  54. package/test/spec/modules/sharedIdSystem_spec.js +52 -6
  55. package/test/spec/modules/targetVideoBidAdapter_spec.js +96 -0
  56. package/test/spec/modules/weboramaRtdProvider_spec.js +408 -214
  57. package/test/spec/modules/yahoosspBidAdapter_spec.js +28 -1
  58. package/test/spec/modules/zeta_global_sspBidAdapter_spec.js +33 -1
  59. package/test/spec/unit/core/targeting_spec.js +72 -0
  60. package/test/spec/utils_spec.js +38 -0
@@ -34,31 +34,34 @@ Module that connects to an Adxcg.com zone to fetch bids.
34
34
  code: 'native-ad-div',
35
35
  mediaTypes: {
36
36
  native: {
37
- image: {
37
+ image: {
38
38
  sendId: false,
39
- required: true,
40
- sizes: [80, 80]
39
+ required: false,
40
+ sizes: [127, 83]
41
41
  },
42
42
  icon: {
43
- sendId: true,
44
- },
45
- brand: {
43
+ sizes: [80, 80],
44
+ required: false,
46
45
  sendId: true,
47
46
  },
48
47
  title: {
49
48
  sendId: false,
50
- required: true,
49
+ required: false,
51
50
  len: 75
52
51
  },
53
52
  body: {
54
53
  sendId: false,
55
- required: true,
54
+ required: false,
56
55
  len: 200
57
56
  },
58
- sponsoredBy: {
57
+ cta: {
59
58
  sendId: false,
60
59
  required: false,
61
- len: 20
60
+ len: 75
61
+ },
62
+ sponsoredBy: {
63
+ sendId: false,
64
+ required: false
62
65
  }
63
66
  }
64
67
  },
@@ -73,21 +76,19 @@ Module that connects to an Adxcg.com zone to fetch bids.
73
76
  code: 'video-div',
74
77
  mediaTypes: {
75
78
  video: {
76
- playerSize: [640, 480],
77
- context: 'instream',
78
- mimes: ['video/mp4'],
79
- protocols: [5, 6, 8],
80
- playback_method: ['auto_play_sound_off']
81
- }
79
+ playerSize: [640, 480],
80
+ context: 'instream',
81
+ mimes: ['video/mp4'],
82
+ protocols: [2, 3, 5, 6, 8],
83
+ playback_method: ['auto_play_sound_off'],
84
+ maxduration: 100,
85
+ skip: 1
86
+ }
82
87
  },
83
88
  bids: [{
84
89
  bidder: 'adxcg',
85
90
  params: {
86
- adzoneid: '20',
87
- video: {
88
- maxduration: 100,
89
- skippable: true
90
- }
91
+ adzoneid: '20'
91
92
  }
92
93
  }]
93
94
  }
@@ -78,7 +78,6 @@ export const spec = {
78
78
  { code: 'districtm', gvlid: 144 },
79
79
  { code: 'adasta' },
80
80
  { code: 'beintoo', gvlid: 618 },
81
- { code: 'targetVideo' },
82
81
  ],
83
82
  supportedMediaTypes: [BANNER, VIDEO, NATIVE],
84
83
 
@@ -99,16 +99,18 @@ export const spec = {
99
99
  }
100
100
 
101
101
  function buildTrackingParams(data, info, value) {
102
+ const accountId = data.params.accountId;
102
103
  return {
103
- pid: data.params.accountId,
104
+ pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId,
104
105
  nid: data.params.networkId,
105
106
  nptnid: data.params.networkPartnerId,
106
- bid: data.bidId,
107
+ bid: data.bidId || data.requestId,
107
108
  sl_n: data.adUnitCode,
108
109
  aid: data.auctionId,
109
110
  se_ca: 'bid',
110
111
  se_ac: info,
111
- se_va: value
112
+ se_va: value,
113
+ url: window.location.href
112
114
  };
113
115
  }
114
116
 
@@ -1,6 +1,7 @@
1
1
  import { getWindowTop, deepAccess, logMessage } from '../src/utils.js';
2
2
  import { registerBidder } from '../src/adapters/bidderFactory.js';
3
3
  import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
4
+ import { ajax } from '../src/ajax.js';
4
5
 
5
6
  const BIDDER_CODE = 'colossusssp';
6
7
  const G_URL = 'https://colossusssp.com/?c=o&m=multi';
@@ -175,6 +176,12 @@ export const spec = {
175
176
  type: 'image',
176
177
  url: G_URL_SYNC
177
178
  }];
179
+ },
180
+
181
+ onBidWon: (bid) => {
182
+ if (bid.nurl) {
183
+ ajax(bid.nurl, null);
184
+ }
178
185
  }
179
186
  };
180
187
 
@@ -24,7 +24,7 @@ const LOG_PREFIX = 'Criteo: ';
24
24
  Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js
25
25
  */
26
26
  const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%';
27
- export const FAST_BID_VERSION_CURRENT = 113;
27
+ export const FAST_BID_VERSION_CURRENT = 116;
28
28
  const FAST_BID_VERSION_LATEST = 'latest';
29
29
  const FAST_BID_VERSION_NONE = 'none';
30
30
  const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js';
@@ -26,6 +26,7 @@ export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.
26
26
  export const CW_PAGE_VIEW_ID = generateUUID();
27
27
  const LS_CWID_KEY = 'cw_cwid';
28
28
  const CW_GROUPS_QUERY = 'cwgroups';
29
+ const CW_CREATIVE_QUERY = 'cwcreative';
29
30
 
30
31
  const storage = getStorageManager();
31
32
 
@@ -161,6 +162,7 @@ export const spec = {
161
162
 
162
163
  let refgroups = [];
163
164
 
165
+ const cwCreativeId = getQueryVariable(CW_CREATIVE_QUERY);
164
166
  const rgQuery = getQueryVariable(CW_GROUPS_QUERY);
165
167
  if (rgQuery !== null) {
166
168
  refgroups = rgQuery.split(',');
@@ -171,6 +173,7 @@ export const spec = {
171
173
  const payload = {
172
174
  cwid: localStorageCWID,
173
175
  refgroups,
176
+ cwcreative: cwCreativeId,
174
177
  slots: slots,
175
178
  httpRef: referer || '',
176
179
  pageViewId: CW_PAGE_VIEW_ID,
@@ -12,7 +12,7 @@ const GVLID = 602;
12
12
  export const spec = {
13
13
  code: BIDDER_CODE,
14
14
  gvlid: GVLID,
15
- aliases: ['dspx'],
15
+ aliases: [],
16
16
  supportedMediaTypes: [BANNER, VIDEO],
17
17
  isBidRequestValid: function(bid) {
18
18
  return !!(bid.params.placement);
@@ -0,0 +1,48 @@
1
+ # Overview
2
+ Module Name: Future Ads Bidder Adapter
3
+ Module Type: Bidder Adapter
4
+ Maintainer: contact@futureads.io
5
+ # Description
6
+ Connects to Future Ads demand source to fetch bids.
7
+ Banner and Video formats are supported.
8
+ Please use ```futureads``` as the bidder code.
9
+ # Test Parameters
10
+ ```
11
+ var adUnits = [
12
+ {
13
+ code: 'desktop-banner-ad-div',
14
+ sizes: [[300, 250]], // a display size
15
+ bids: [
16
+ {
17
+ bidder: "futureads",
18
+ params: {
19
+ zone: '2eb6bd58-865c-47ce-af7f-a918108c3fd2'
20
+ }
21
+ }
22
+ ]
23
+ },{
24
+ code: 'mobile-banner-ad-div',
25
+ sizes: [[300, 50]], // a mobile size
26
+ bids: [
27
+ {
28
+ bidder: "futureads",
29
+ params: {
30
+ zone: '62211486-c50b-4356-9f0f-411778d31fcc'
31
+ }
32
+ }
33
+ ]
34
+ },{
35
+ code: 'video-ad',
36
+ sizes: [[300, 50]],
37
+ mediaType: 'video',
38
+ bids: [
39
+ {
40
+ bidder: "futureads",
41
+ params: {
42
+ zone: 'ebeb1e79-8cb4-4473-b2d0-2e24b7ff47fd'
43
+ }
44
+ }
45
+ ]
46
+ },
47
+ ];
48
+ ```
@@ -138,11 +138,17 @@ function getReferer(bidderRequest) {
138
138
  function getGdprConsentChoice(bidderRequest) {
139
139
  const hasGdprConsent =
140
140
  hasValue(bidderRequest) &&
141
- hasValue(bidderRequest.gdprConsent) &&
142
- hasStringValue(bidderRequest.gdprConsent.consentString)
141
+ hasValue(bidderRequest.gdprConsent)
143
142
 
144
143
  if (hasGdprConsent) {
145
- return bidderRequest.gdprConsent
144
+ const gdprConsent = bidderRequest.gdprConsent
145
+ const hasGdprApplies = hasBooleanValue(gdprConsent.gdprApplies)
146
+
147
+ return {
148
+ consentString: gdprConsent.consentString || '',
149
+ vendorData: gdprConsent.vendorData || {},
150
+ gdprApplies: hasGdprApplies ? gdprConsent.gdprApplies : true,
151
+ }
146
152
  }
147
153
 
148
154
  return {
@@ -177,6 +183,13 @@ function hasValue(value) {
177
183
  )
178
184
  }
179
185
 
186
+ function hasBooleanValue(value) {
187
+ return (
188
+ hasValue(value) &&
189
+ typeof value === 'boolean'
190
+ )
191
+ }
192
+
180
193
  function hasStringValue(value) {
181
194
  return (
182
195
  hasValue(value) &&
@@ -296,8 +296,8 @@ function buildRequests(validBidRequests, bidderRequest) {
296
296
  let gpid = '';
297
297
 
298
298
  const date = new Date();
299
- const lt = date && date.getTime();
300
- const to = date && date.getTimezoneOffset();
299
+ const lt = date.getTime();
300
+ const to = date.getTimezoneOffset();
301
301
  if (to) {
302
302
  lt && (data.lt = lt);
303
303
  data.to = to;
@@ -180,7 +180,7 @@ function kinessoSyncUrl(accountId, consentData) {
180
180
  const usPrivacyString = uspDataHandler.getConsentData();
181
181
  let kinessoSyncUrl = `${ID_SVC}?accountid=${accountId}`;
182
182
  if (usPrivacyString) {
183
- kinessoSyncUrl = `${kinessoSyncUrl}?us_privacy=${usPrivacyString}`;
183
+ kinessoSyncUrl = `${kinessoSyncUrl}&us_privacy=${usPrivacyString}`;
184
184
  }
185
185
  if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return kinessoSyncUrl;
186
186
 
@@ -4,10 +4,20 @@
4
4
  * @module modules/lotamePanoramaId
5
5
  * @requires module:modules/userId
6
6
  */
7
- import { timestamp, isStr, logError, isBoolean, buildUrl, isEmpty, isArray } from '../src/utils.js';
7
+ import {
8
+ timestamp,
9
+ isStr,
10
+ logError,
11
+ isBoolean,
12
+ buildUrl,
13
+ isEmpty,
14
+ isArray,
15
+ isEmptyStr
16
+ } from '../src/utils.js';
8
17
  import { ajax } from '../src/ajax.js';
9
18
  import { submodule } from '../src/hook.js';
10
19
  import { getStorageManager } from '../src/storageManager.js';
20
+ import { uspDataHandler } from '../src/adapterManager.js';
11
21
 
12
22
  const KEY_ID = 'panoramaId';
13
23
  const KEY_EXPIRY = `${KEY_ID}_expiry`;
@@ -115,14 +125,23 @@ function saveLotameCache(
115
125
 
116
126
  /**
117
127
  * Retrieve all the cached values from cookies and/or local storage
128
+ * @param {Number} clientId
118
129
  */
119
- function getLotameLocalCache() {
130
+ function getLotameLocalCache(clientId = undefined) {
120
131
  let cache = {
121
132
  data: getFromStorage(KEY_ID),
122
133
  expiryTimestampMs: 0,
134
+ clientExpiryTimestampMs: 0,
123
135
  };
124
136
 
125
137
  try {
138
+ if (clientId) {
139
+ const rawClientExpiry = getFromStorage(`${KEY_EXPIRY}_${clientId}`);
140
+ if (isStr(rawClientExpiry)) {
141
+ cache.clientExpiryTimestampMs = parseInt(rawClientExpiry, 10);
142
+ }
143
+ }
144
+
126
145
  const rawExpiry = getFromStorage(KEY_EXPIRY);
127
146
  if (isStr(rawExpiry)) {
128
147
  cache.expiryTimestampMs = parseInt(rawExpiry, 10);
@@ -191,11 +210,25 @@ export const lotamePanoramaIdSubmodule = {
191
210
  */
192
211
  getId(config, consentData, cacheIdObj) {
193
212
  cookieDomain = lotamePanoramaIdSubmodule.findRootDomain();
194
- let localCache = getLotameLocalCache();
213
+ const configParams = (config && config.params) || {};
214
+ const clientId = configParams.clientId;
215
+ const hasCustomClientId = !isEmpty(clientId);
216
+ const localCache = getLotameLocalCache(clientId);
195
217
 
196
- let refreshNeeded = Date.now() > localCache.expiryTimestampMs;
218
+ const hasExpiredPanoId = Date.now() > localCache.expiryTimestampMs;
219
+
220
+ if (hasCustomClientId) {
221
+ const hasFreshClientNoConsent = Date.now() < localCache.clientExpiryTimestampMs;
222
+ if (hasFreshClientNoConsent) {
223
+ // There is no consent
224
+ return {
225
+ id: undefined,
226
+ reason: 'NO_CLIENT_CONSENT',
227
+ };
228
+ }
229
+ }
197
230
 
198
- if (!refreshNeeded) {
231
+ if (!hasExpiredPanoId) {
199
232
  return {
200
233
  id: localCache.data,
201
234
  };
@@ -203,6 +236,18 @@ export const lotamePanoramaIdSubmodule = {
203
236
 
204
237
  const storedUserId = getProfileId();
205
238
 
239
+ // Add CCPA Consent data handling
240
+ const usp = uspDataHandler.getConsentData();
241
+
242
+ let usPrivacy;
243
+ if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) {
244
+ usPrivacy = usp;
245
+ }
246
+ if (!usPrivacy) {
247
+ // fallback to 1st party cookie
248
+ usPrivacy = getFromStorage('us_privacy');
249
+ }
250
+
206
251
  const resolveIdFunction = function (callback) {
207
252
  let queryParams = {};
208
253
  if (storedUserId) {
@@ -226,6 +271,17 @@ export const lotamePanoramaIdSubmodule = {
226
271
  if (consentString) {
227
272
  queryParams.gdpr_consent = consentString;
228
273
  }
274
+
275
+ // Add usPrivacy to the url
276
+ if (usPrivacy) {
277
+ queryParams.us_privacy = usPrivacy;
278
+ }
279
+
280
+ // Add clientId to the url
281
+ if (hasCustomClientId) {
282
+ queryParams.c = clientId;
283
+ }
284
+
229
285
  const url = buildUrl({
230
286
  protocol: 'https',
231
287
  host: `id.crwdcntrl.net`,
@@ -239,15 +295,31 @@ export const lotamePanoramaIdSubmodule = {
239
295
  if (response) {
240
296
  try {
241
297
  let responseObj = JSON.parse(response);
242
- const shouldUpdateProfileId = !(
298
+ const hasNoConsentErrors = !(
243
299
  isArray(responseObj.errors) &&
244
300
  responseObj.errors.indexOf(MISSING_CORE_CONSENT) !== -1
245
301
  );
246
302
 
303
+ if (hasCustomClientId) {
304
+ if (hasNoConsentErrors) {
305
+ clearLotameCache(`${KEY_EXPIRY}_${clientId}`);
306
+ } else if (isStr(responseObj.no_consent) && responseObj.no_consent === 'CLIENT') {
307
+ saveLotameCache(
308
+ `${KEY_EXPIRY}_${clientId}`,
309
+ responseObj.expiry_ts,
310
+ responseObj.expiry_ts
311
+ );
312
+
313
+ // End Processing
314
+ callback();
315
+ return;
316
+ }
317
+ }
318
+
247
319
  saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts, responseObj.expiry_ts);
248
320
 
249
321
  if (isStr(responseObj.profile_id)) {
250
- if (shouldUpdateProfileId) {
322
+ if (hasNoConsentErrors) {
251
323
  setProfileId(responseObj.profile_id);
252
324
  }
253
325
 
@@ -262,7 +334,7 @@ export const lotamePanoramaIdSubmodule = {
262
334
  clearLotameCache(KEY_ID);
263
335
  }
264
336
  } else {
265
- if (shouldUpdateProfileId) {
337
+ if (hasNoConsentErrors) {
266
338
  clearLotameCache(KEY_PROFILE);
267
339
  }
268
340
  clearLotameCache(KEY_ID);
@@ -7,7 +7,6 @@ const BIDDER_CODE = 'mediasquare';
7
7
  const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/'
8
8
  const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/'
9
9
  const BIDDER_ENDPOINT_AUCTION = 'msq_prebid';
10
- const BIDDER_ENDPOINT_SYNC = 'cookie_sync';
11
10
  const BIDDER_ENDPOINT_WINNING = 'winning';
12
11
 
13
12
  export const spec = {
@@ -132,18 +131,11 @@ export const spec = {
132
131
  * @return {UserSync[]} The user syncs which should be dropped.
133
132
  */
134
133
  getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
135
- let params = '';
136
- let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD;
137
134
  if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') &&
138
135
  serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') {
139
136
  return serverResponses[0].body.cookies;
140
137
  } else {
141
- if (gdprConsent && typeof gdprConsent.consentString === 'string') { params += typeof gdprConsent.gdprApplies === 'boolean' ? `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` : `&gdpr_consent=${gdprConsent.consentString}`; }
142
- if (uspConsent && typeof uspConsent === 'string') { params += '&uspConsent=' + uspConsent }
143
- return {
144
- type: 'iframe',
145
- url: endpoint + BIDDER_ENDPOINT_SYNC + '?type=iframe' + params
146
- };
138
+ return [];
147
139
  }
148
140
  },
149
141
 
@@ -4,6 +4,7 @@ import { BANNER } from '../src/mediaTypes.js';
4
4
 
5
5
  const BIDDER_CODE = 'nextMillennium';
6
6
  const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction';
7
+ const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4';
7
8
  const TIME_TO_LIVE = 360;
8
9
 
9
10
  export const spec = {
@@ -18,10 +19,13 @@ export const spec = {
18
19
 
19
20
  buildRequests: function(validBidRequests, bidderRequest) {
20
21
  const requests = [];
22
+ window.nmmRefreshCounts = window.nmmRefreshCounts || {};
21
23
 
22
24
  _each(validBidRequests, function(bid) {
25
+ window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0;
23
26
  const postBody = {
24
27
  'id': bid.auctionId,
28
+ 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++,
25
29
  'ext': {
26
30
  'prebid': {
27
31
  'storedrequest': {
@@ -89,7 +93,31 @@ export const spec = {
89
93
  });
90
94
 
91
95
  return bidResponses;
92
- }
96
+ },
97
+
98
+ getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) {
99
+ if (!syncOptions.iframeEnabled) {
100
+ return
101
+ }
102
+
103
+ let syncurl = gdprConsent && gdprConsent.gdprApplies ? `${SYNC_ENDPOINT}&gdpr=1&gdpr_consent=${gdprConsent.consentString}` : SYNC_ENDPOINT
104
+
105
+ let bidders = []
106
+ if (responses) {
107
+ _each(responses, (response) => {
108
+ _each(Object.keys(response.body.ext.responsetimemillis), b => bidders.push(b))
109
+ })
110
+ }
111
+
112
+ if (bidders.length) {
113
+ syncurl += `&bidders=${bidders.join(',')}`
114
+ }
115
+
116
+ return [{
117
+ type: 'iframe',
118
+ url: syncurl
119
+ }];
120
+ },
93
121
  };
94
122
 
95
123
  registerBidder(spec);
@@ -56,6 +56,7 @@ let eidPermissions;
56
56
  * @property {string} [adapter='prebidServer'] adapter code to use for S2S
57
57
  * @property {boolean} [enabled=false] enables S2S bidding
58
58
  * @property {number} [timeout=1000] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})`
59
+ * @property {number} [syncTimeout=1000] timeout for cookie sync iframe / image rendering
59
60
  * @property {number} [maxBids=1]
60
61
  * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server
61
62
  * @property {Object} [syncUrlModifier]
@@ -77,6 +78,7 @@ let eidPermissions;
77
78
  */
78
79
  const s2sDefaultConfig = {
79
80
  timeout: 1000,
81
+ syncTimeout: 1000,
80
82
  maxBids: 1,
81
83
  adapter: 'prebidServer',
82
84
  adapterOptions: {},
@@ -274,11 +276,9 @@ function doAllSyncs(bidders, s2sConfig) {
274
276
  */
275
277
  function doPreBidderSync(type, url, bidder, done, s2sConfig) {
276
278
  if (s2sConfig.syncUrlModifier && typeof s2sConfig.syncUrlModifier[bidder] === 'function') {
277
- const newSyncUrl = s2sConfig.syncUrlModifier[bidder](type, url, bidder);
278
- doBidderSync(type, newSyncUrl, bidder, done)
279
- } else {
280
- doBidderSync(type, url, bidder, done)
279
+ url = s2sConfig.syncUrlModifier[bidder](type, url, bidder);
281
280
  }
281
+ doBidderSync(type, url, bidder, done, s2sConfig.syncTimeout)
282
282
  }
283
283
 
284
284
  /**
@@ -288,17 +288,18 @@ function doPreBidderSync(type, url, bidder, done, s2sConfig) {
288
288
  * @param {string} url the url to sync
289
289
  * @param {string} bidder name of bidder doing sync for
290
290
  * @param {function} done an exit callback; to signify this pixel has either: finished rendering or something went wrong
291
+ * @param {number} timeout: maximum time to wait for rendering in milliseconds
291
292
  */
292
- function doBidderSync(type, url, bidder, done) {
293
+ function doBidderSync(type, url, bidder, done, timeout) {
293
294
  if (!url) {
294
295
  logError(`No sync url for bidder "${bidder}": ${url}`);
295
296
  done();
296
297
  } else if (type === 'image' || type === 'redirect') {
297
298
  logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`);
298
- triggerPixel(url, done);
299
- } else if (type == 'iframe') {
299
+ triggerPixel(url, done, timeout);
300
+ } else if (type === 'iframe') {
300
301
  logMessage(`Invoking iframe user sync for bidder: "${bidder}"`);
301
- insertUserSyncIframe(url, done);
302
+ insertUserSyncIframe(url, done, timeout);
302
303
  } else {
303
304
  logError(`User sync type "${type}" not supported for bidder: "${bidder}"`);
304
305
  done();
@@ -539,10 +540,13 @@ const OPEN_RTB_PROTOCOL = {
539
540
  }
540
541
  if (Array.isArray(params.aspect_ratios)) {
541
542
  // pass aspect_ratios as ext data I guess?
542
- asset.ext = {
543
- aspectratios: params.aspect_ratios.map(
544
- ratio => `${ratio.ratio_width}:${ratio.ratio_height}`
545
- )
543
+ const aspectRatios = params.aspect_ratios
544
+ .filter((ar) => ar.ratio_width && ar.ratio_height)
545
+ .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`);
546
+ if (aspectRatios.length > 0) {
547
+ asset.ext = {
548
+ aspectratios: aspectRatios
549
+ }
546
550
  }
547
551
  }
548
552
  assets.push(newAsset({
@@ -58,11 +58,10 @@ export const spec = {
58
58
  REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null)
59
59
 
60
60
  payload.gdpr_consent = '';
61
- payload.gdpr = null;
61
+ payload.gdpr = bidderRequest.gdprConsent.gdprApplies;
62
62
 
63
63
  if (bidderRequest && bidderRequest.gdprConsent) {
64
64
  payload.gdpr_consent = bidderRequest.gdprConsent.consentString;
65
- payload.gdpr = bidderRequest.gdprConsent.gdprApplies;
66
65
  }
67
66
 
68
67
  var payloadString = JSON.stringify(payload);