prebid.js 5.19.0 → 5.20.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 (53) hide show
  1. package/modules/airgridRtdProvider.js +1 -1
  2. package/modules/appnexusBidAdapter.js +5 -3
  3. package/modules/atsAnalyticsAdapter.js +67 -46
  4. package/modules/atsAnalyticsAdapter.md +1 -0
  5. package/modules/betweenBidAdapter.js +2 -1
  6. package/modules/browsiRtdProvider.js +106 -18
  7. package/modules/cleanioRtdProvider.js +192 -0
  8. package/modules/cleanioRtdProvider.md +59 -0
  9. package/modules/deltaprojectsBidAdapter.js +252 -0
  10. package/modules/deltaprojectsBidAdapter.md +32 -0
  11. package/modules/gridBidAdapter.js +1 -0
  12. package/modules/ixBidAdapter.js +7 -1
  13. package/modules/jixieBidAdapter.js +8 -2
  14. package/modules/justpremiumBidAdapter.js +6 -1
  15. package/modules/livewrappedAnalyticsAdapter.js +5 -0
  16. package/modules/multibid/index.js +3 -3
  17. package/modules/nativoBidAdapter.js +5 -1
  18. package/modules/openxBidAdapter.js +1 -1
  19. package/modules/operaadsBidAdapter.js +21 -1
  20. package/modules/otmBidAdapter.js +146 -0
  21. package/modules/otmBidAdapter.md +27 -26
  22. package/modules/outbrainBidAdapter.js +5 -0
  23. package/modules/playwireBidAdapter.md +61 -0
  24. package/modules/rtdModule/index.js +2 -2
  25. package/modules/sonobiBidAdapter.js +7 -0
  26. package/modules/sortableBidAdapter.js +1 -0
  27. package/modules/teadsBidAdapter.js +3 -0
  28. package/modules/trustxBidAdapter.js +8 -6
  29. package/modules/ventesBidAdapter.js +370 -0
  30. package/modules/ventesBidAdapter.md +94 -0
  31. package/modules/yahoosspBidAdapter.js +6 -6
  32. package/package.json +1 -1
  33. package/src/auction.js +11 -11
  34. package/test/spec/modules/appnexusBidAdapter_spec.js +2 -1
  35. package/test/spec/modules/atsAnalyticsAdapter_spec.js +42 -9
  36. package/test/spec/modules/browsiRtdProvider_spec.js +62 -7
  37. package/test/spec/modules/cleanioRtdProvider_spec.js +188 -0
  38. package/test/spec/modules/deltaprojectsBidAdapter_spec.js +399 -0
  39. package/test/spec/modules/ixBidAdapter_spec.js +3 -3
  40. package/test/spec/modules/jixieBidAdapter_spec.js +13 -11
  41. package/test/spec/modules/justpremiumBidAdapter_spec.js +9 -2
  42. package/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +23 -4
  43. package/test/spec/modules/multibid_spec.js +31 -31
  44. package/test/spec/modules/openxBidAdapter_spec.js +0 -26
  45. package/test/spec/modules/operaadsBidAdapter_spec.js +38 -6
  46. package/test/spec/modules/otmBidAdapter_spec.js +67 -0
  47. package/test/spec/modules/outbrainBidAdapter_spec.js +18 -0
  48. package/test/spec/modules/sonobiBidAdapter_spec.js +34 -1
  49. package/test/spec/modules/sortableBidAdapter_spec.js +11 -0
  50. package/test/spec/modules/teadsBidAdapter_spec.js +132 -0
  51. package/test/spec/modules/trustxBidAdapter_spec.js +3 -3
  52. package/test/spec/modules/ventesBidAdapter_spec.js +845 -0
  53. package/test/spec/unit/core/adapterManager_spec.js +2 -1
@@ -33,7 +33,7 @@ export function attachScriptTagToDOM(rtdConfig) {
33
33
  edktInitializor.load = function(e) {
34
34
  var p = e || 'sdk';
35
35
  var n = document.createElement('script');
36
- n.type = 'text/javascript';
36
+ n.type = 'module';
37
37
  n.async = true;
38
38
  n.src = 'https://cdn.edkt.io/' + p + '/edgekit.min.js';
39
39
  document.getElementsByTagName('head')[0].appendChild(n);
@@ -696,9 +696,11 @@ function newBid(serverBid, rtbBid, bidderRequest) {
696
696
  });
697
697
  try {
698
698
  if (rtbBid.rtb.trackers) {
699
- const url = rtbBid.rtb.trackers[0].impression_urls[0];
700
- const tracker = createTrackPixelHtml(url);
701
- bid.ad += tracker;
699
+ for (let i = 0; i < rtbBid.rtb.trackers[0].impression_urls.length; i++) {
700
+ const url = rtbBid.rtb.trackers[0].impression_urls[i];
701
+ const tracker = createTrackPixelHtml(url);
702
+ bid.ad += tracker;
703
+ }
702
704
  }
703
705
  } catch (error) {
704
706
  logError('Error appending tracking pixel', error);
@@ -20,7 +20,7 @@ export const analyticsUrl = 'https://analytics.rlcdn.com';
20
20
  let handlerRequest = [];
21
21
  let handlerResponse = [];
22
22
 
23
- let atsAnalyticsAdapterVersion = 2;
23
+ let atsAnalyticsAdapterVersion = 3;
24
24
 
25
25
  let browsersList = [
26
26
  /* Googlebot */
@@ -222,7 +222,8 @@ function bidRequestedHandler(args) {
222
222
  auction_start: new Date(args.auctionStart).toJSON(),
223
223
  domain: window.location.hostname,
224
224
  pid: atsAnalyticsAdapter.context.pid,
225
- adapter_version: atsAnalyticsAdapterVersion
225
+ adapter_version: atsAnalyticsAdapterVersion,
226
+ bid_won: false
226
227
  };
227
228
  });
228
229
  return requests;
@@ -251,13 +252,14 @@ export function parseBrowser() {
251
252
  }
252
253
  }
253
254
 
254
- function sendDataToAnalytic () {
255
+ function sendDataToAnalytic (events) {
255
256
  // send data to ats analytic endpoint
256
257
  try {
257
- let dataToSend = {'Data': atsAnalyticsAdapter.context.events};
258
+ let dataToSend = {'Data': events};
258
259
  let strJSON = JSON.stringify(dataToSend);
259
260
  logInfo('ATS Analytics - tried to send analytics data!');
260
261
  ajax(analyticsUrl, function () {
262
+ logInfo('ATS Analytics - events sent successfully!');
261
263
  }, strJSON, {method: 'POST', contentType: 'application/json'});
262
264
  } catch (err) {
263
265
  logError('ATS Analytics - request encounter an error: ', err);
@@ -265,7 +267,7 @@ function sendDataToAnalytic () {
265
267
  }
266
268
 
267
269
  // preflight request, to check did publisher have permission to send data to analytics endpoint
268
- function preflightRequest (envelopeSourceCookieValue) {
270
+ function preflightRequest (envelopeSourceCookieValue, events) {
269
271
  logInfo('ATS Analytics - preflight request!');
270
272
  ajax(preflightUrl + atsAnalyticsAdapter.context.pid,
271
273
  {
@@ -276,7 +278,8 @@ function preflightRequest (envelopeSourceCookieValue) {
276
278
  atsAnalyticsAdapter.setSamplingCookie(samplingRate);
277
279
  let samplingRateNumber = Number(samplingRate);
278
280
  if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) {
279
- sendDataToAnalytic();
281
+ logInfo('ATS Analytics - events to send: ', events);
282
+ sendDataToAnalytic(events);
280
283
  }
281
284
  },
282
285
  error: function () {
@@ -286,29 +289,6 @@ function preflightRequest (envelopeSourceCookieValue) {
286
289
  }, undefined, {method: 'GET', crossOrigin: true});
287
290
  }
288
291
 
289
- function callHandler(evtype, args) {
290
- if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) {
291
- handlerRequest = handlerRequest.concat(bidRequestedHandler(args));
292
- } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) {
293
- handlerResponse.push(bidResponseHandler(args));
294
- }
295
- if (evtype === CONSTANTS.EVENTS.AUCTION_END) {
296
- if (handlerRequest.length) {
297
- let events = [];
298
- if (handlerResponse.length) {
299
- events = handlerRequest.filter(request => handlerResponse.filter(function(response) {
300
- if (request.bid_id === response.bid_id) {
301
- Object.assign(request, response);
302
- }
303
- }));
304
- } else {
305
- events = handlerRequest;
306
- }
307
- atsAnalyticsAdapter.context.events = events;
308
- }
309
- }
310
- }
311
-
312
292
  let atsAnalyticsAdapter = Object.assign(adapter(
313
293
  {
314
294
  analyticsType
@@ -316,22 +296,7 @@ let atsAnalyticsAdapter = Object.assign(adapter(
316
296
  {
317
297
  track({eventType, args}) {
318
298
  if (typeof args !== 'undefined') {
319
- callHandler(eventType, args);
320
- }
321
- if (eventType === CONSTANTS.EVENTS.AUCTION_END) {
322
- let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats');
323
- try {
324
- let samplingRateCookie = storage.getCookie('_lr_sampling_rate');
325
- if (!samplingRateCookie) {
326
- preflightRequest(envelopeSourceCookieValue);
327
- } else {
328
- if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) {
329
- sendDataToAnalytic();
330
- }
331
- }
332
- } catch (err) {
333
- logError('ATS Analytics - preflight request encounter an error: ', err);
334
- }
299
+ atsAnalyticsAdapter.callHandler(eventType, args);
335
300
  }
336
301
  }
337
302
  });
@@ -369,13 +334,69 @@ atsAnalyticsAdapter.enableAnalytics = function (config) {
369
334
  }
370
335
  atsAnalyticsAdapter.context = {
371
336
  events: [],
372
- pid: config.options.pid
337
+ pid: config.options.pid,
338
+ bidWonTimeout: config.options.bidWonTimeout
373
339
  };
374
340
  let initOptions = config.options;
375
341
  logInfo('ATS Analytics - adapter enabled! ');
376
342
  atsAnalyticsAdapter.originEnableAnalytics(initOptions); // call the base class function
377
343
  };
378
344
 
345
+ atsAnalyticsAdapter.callHandler = function (evtype, args) {
346
+ if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) {
347
+ handlerRequest = handlerRequest.concat(bidRequestedHandler(args));
348
+ } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) {
349
+ handlerResponse.push(bidResponseHandler(args));
350
+ }
351
+ if (evtype === CONSTANTS.EVENTS.AUCTION_END) {
352
+ let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000;
353
+ let events = [];
354
+ setTimeout(() => {
355
+ let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids();
356
+ logInfo('ATS Analytics - winning bids: ', winningBids)
357
+ // prepare format data for sending to analytics endpoint
358
+ if (handlerRequest.length) {
359
+ let wonEvent = {};
360
+ if (handlerResponse.length) {
361
+ events = handlerRequest.filter(request => handlerResponse.filter(function (response) {
362
+ if (request.bid_id === response.bid_id) {
363
+ Object.assign(request, response);
364
+ }
365
+ }));
366
+ if (winningBids.length) {
367
+ events = events.filter(event => winningBids.filter(function (won) {
368
+ wonEvent.bid_id = won.requestId;
369
+ wonEvent.bid_won = true;
370
+ if (event.bid_id === wonEvent.bid_id) {
371
+ Object.assign(event, wonEvent);
372
+ }
373
+ }))
374
+ }
375
+ } else {
376
+ events = handlerRequest;
377
+ }
378
+ // check should we send data to analytics or not, check first cookie value _lr_sampling_rate
379
+ try {
380
+ let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats');
381
+ let samplingRateCookie = storage.getCookie('_lr_sampling_rate');
382
+ if (!samplingRateCookie) {
383
+ preflightRequest(envelopeSourceCookieValue, events);
384
+ } else {
385
+ if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) {
386
+ logInfo('ATS Analytics - events to send: ', events);
387
+ sendDataToAnalytic(events);
388
+ }
389
+ }
390
+ // empty events array to not send duplicate events
391
+ events = [];
392
+ } catch (err) {
393
+ logError('ATS Analytics - preflight request encounter an error: ', err);
394
+ }
395
+ }
396
+ }, bidWonTimeout);
397
+ }
398
+ }
399
+
379
400
  adaptermanager.registerAnalyticsAdapter({
380
401
  adapter: atsAnalyticsAdapter,
381
402
  code: 'atsAnalytics',
@@ -17,6 +17,7 @@ Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp.
17
17
  provider: 'atsAnalytics',
18
18
  options: {
19
19
  pid: '999', // publisher ID
20
+ bidWonTimeout: 2000 // on auction end for how long to wait for bid_won events, by default it's 2000 miliseconds, if it's not set it will be 2000 miliseconds.
20
21
  }
21
22
  }
22
23
  ```
@@ -6,6 +6,7 @@ const BIDDER_CODE = 'between';
6
6
  let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid';
7
7
  const CODE_TYPES = ['inpage', 'preroll', 'midroll', 'postroll'];
8
8
 
9
+ const includes = require('core-js-pure/features/array/includes.js');
9
10
  export const spec = {
10
11
  code: BIDDER_CODE,
11
12
  aliases: ['btw'],
@@ -53,7 +54,7 @@ export const spec = {
53
54
  params.mind = video.mind;
54
55
  params.pos = 'atf';
55
56
  ENDPOINT += '&jst=pvc';
56
- params.codeType = CODE_TYPES.includes(video.codeType) ? video.codeType : 'inpage';
57
+ params.codeType = includes(CODE_TYPES, video.codeType) ? video.codeType : 'inpage';
57
58
  }
58
59
 
59
60
  if (i.params.itu !== undefined) {
@@ -15,21 +15,27 @@
15
15
  * @property {?string} keyName
16
16
  */
17
17
 
18
- import { deepClone, logError, isGptPubadsDefined } from '../src/utils.js';
18
+ import { deepClone, logError, isGptPubadsDefined, isNumber, isFn, deepSetValue } from '../src/utils.js';
19
19
  import {submodule} from '../src/hook.js';
20
20
  import {ajaxBuilder} from '../src/ajax.js';
21
21
  import {loadExternalScript} from '../src/adloader.js';
22
22
  import {getStorageManager} from '../src/storageManager.js';
23
23
  import find from 'core-js-pure/features/array/find.js';
24
+ import {getGlobal} from '../src/prebidGlobal.js';
25
+ import includes from 'core-js-pure/features/array/includes.js';
24
26
 
25
27
  const storage = getStorageManager();
26
28
 
27
29
  /** @type {ModuleParams} */
28
30
  let _moduleParams = {};
29
31
  /** @type {null|Object} */
30
- let _predictionsData = null;
32
+ let _browsiData = null;
31
33
  /** @type {string} */
32
34
  const DEF_KEYNAME = 'browsiViewability';
35
+ /** @type {null | function} */
36
+ let _dataReadyCallback = null;
37
+ /** @type {null|Object} */
38
+ let _ic = {};
33
39
 
34
40
  /**
35
41
  * add browsi script to page
@@ -78,29 +84,49 @@ export function collectData() {
78
84
  getPredictionsFromServer(`//${_moduleParams.url}/prebid?${toUrlParams(predictorData)}`);
79
85
  }
80
86
 
87
+ /**
88
+ * wait for data from server
89
+ * call callback when data is ready
90
+ * @param {function} callback
91
+ */
92
+ function waitForData(callback) {
93
+ if (_browsiData) {
94
+ _dataReadyCallback = null;
95
+ callback(_browsiData);
96
+ } else {
97
+ _dataReadyCallback = callback;
98
+ }
99
+ }
100
+
81
101
  export function setData(data) {
82
- _predictionsData = data;
102
+ _browsiData = data;
103
+ if (isFn(_dataReadyCallback)) {
104
+ _dataReadyCallback(_browsiData);
105
+ _dataReadyCallback = null;
106
+ }
83
107
  }
84
108
 
85
- function sendDataToModule(adUnitsCodes) {
109
+ function getRTD(auc) {
86
110
  try {
87
- const _predictions = (_predictionsData && _predictionsData.p) || {};
88
- return adUnitsCodes.reduce((rp, adUnitCode) => {
89
- if (!adUnitCode) {
111
+ const _bp = (_browsiData && _browsiData.p) || {};
112
+ return auc.reduce((rp, uc) => {
113
+ _ic[uc] = _ic[uc] || 0;
114
+ const _c = _ic[uc];
115
+ if (!uc) {
90
116
  return rp
91
117
  }
92
- const adSlot = getSlotByCode(adUnitCode);
93
- const identifier = adSlot ? getMacroId(_predictionsData['pmd'], adSlot) : adUnitCode;
94
- const predictionData = _predictions[identifier];
95
- rp[adUnitCode] = getKVObject(-1, _predictionsData['kn']);
96
- if (!predictionData) {
118
+ const adSlot = getSlotByCode(uc);
119
+ const identifier = adSlot ? getMacroId(_browsiData['pmd'], adSlot) : uc;
120
+ const _pd = _bp[identifier];
121
+ rp[uc] = getKVObject(-1);
122
+ if (!_pd) {
97
123
  return rp
98
124
  }
99
- if (predictionData.p) {
100
- if (!isIdMatchingAdUnit(adSlot, predictionData.w)) {
125
+ if (_pd.ps) {
126
+ if (!isIdMatchingAdUnit(adSlot, _pd.w)) {
101
127
  return rp;
102
128
  }
103
- rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn);
129
+ rp[uc] = getKVObject(getCurrentData(_pd.ps, _c));
104
130
  }
105
131
  return rp;
106
132
  }, {});
@@ -109,6 +135,31 @@ function sendDataToModule(adUnitsCodes) {
109
135
  }
110
136
  }
111
137
 
138
+ /**
139
+ * get prediction
140
+ * return -1 if prediction not found
141
+ * @param {object} predictionObject
142
+ * @param {number} _c
143
+ * @return {number}
144
+ */
145
+ export function getCurrentData(predictionObject, _c) {
146
+ if (!predictionObject || !isNumber(_c)) {
147
+ return -1;
148
+ }
149
+ if (isNumber(predictionObject[_c])) {
150
+ return predictionObject[_c];
151
+ }
152
+ if (Object.keys(predictionObject).length > 1) {
153
+ while (_c > 0) {
154
+ _c--;
155
+ if (isNumber(predictionObject[_c])) {
156
+ return predictionObject[_c];
157
+ }
158
+ }
159
+ }
160
+ return -1;
161
+ }
162
+
112
163
  /**
113
164
  * get all slots on page
114
165
  * @return {Object[]} slot GoogleTag slots
@@ -122,12 +173,16 @@ function getAllSlots() {
122
173
  * @param {string?} keyName
123
174
  * @return {Object} key:value
124
175
  */
125
- function getKVObject(p, keyName) {
176
+ function getKVObject(p) {
126
177
  const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2);
127
178
  let prObject = {};
128
- prObject[((_moduleParams['keyName'] || keyName || DEF_KEYNAME).toString())] = prValue.toString();
179
+ prObject[getKey()] = prValue.toString();
129
180
  return prObject;
130
181
  }
182
+
183
+ function getKey() {
184
+ return ((_moduleParams['keyName'] || (_browsiData && _browsiData['kn']) || DEF_KEYNAME).toString())
185
+ }
131
186
  /**
132
187
  * check if placement id matches one of given ad units
133
188
  * @param {Object} slot google slot
@@ -238,6 +293,28 @@ function toUrlParams(data) {
238
293
  .join('&');
239
294
  }
240
295
 
296
+ function setBidRequestsData(bidObj, callback) {
297
+ let adUnitCodes = bidObj.adUnitCodes;
298
+ let adUnits = bidObj.adUnits || getGlobal().adUnits || [];
299
+ if (adUnitCodes) {
300
+ adUnits = adUnits.filter(au => includes(adUnitCodes, au.code));
301
+ } else {
302
+ adUnitCodes = adUnits.map(au => au.code);
303
+ }
304
+ waitForData(() => {
305
+ const data = getRTD(adUnitCodes);
306
+ if (data) {
307
+ adUnits.forEach(adUnit => {
308
+ const adUnitCode = adUnit.code;
309
+ if (data[adUnitCode]) {
310
+ deepSetValue(adUnit, 'ortb2Imp.ext.data.browsi', {[getKey()]: data[adUnitCode][getKey()]});
311
+ }
312
+ });
313
+ }
314
+ callback();
315
+ })
316
+ }
317
+
241
318
  /** @type {RtdSubmodule} */
242
319
  export const browsiSubmodule = {
243
320
  /**
@@ -250,10 +327,21 @@ export const browsiSubmodule = {
250
327
  * @function
251
328
  * @param {string[]} adUnitsCodes
252
329
  */
253
- getTargetingData: sendDataToModule,
330
+ getTargetingData: getTargetingData,
254
331
  init: init,
332
+ getBidRequestData: setBidRequestsData
255
333
  };
256
334
 
335
+ function getTargetingData(uc) {
336
+ const targetingData = getRTD(uc);
337
+ uc.forEach(auc => {
338
+ if (isNumber(_ic[auc])) {
339
+ _ic[auc] = _ic[auc] + 1;
340
+ }
341
+ });
342
+ return targetingData;
343
+ }
344
+
257
345
  function init(moduleConfig) {
258
346
  _moduleParams = moduleConfig.params;
259
347
  if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) {
@@ -0,0 +1,192 @@
1
+ /**
2
+ * This module adds clean.io provider to the real time data module
3
+ * The {@link module:modules/realTimeData} module is required
4
+ * The module will wrap bid responses markup in clean.io agent script for protection
5
+ * @module modules/cleanioRtdProvider
6
+ * @requires module:modules/realTimeData
7
+ */
8
+
9
+ import { submodule } from '../src/hook.js';
10
+ import { logError, generateUUID, insertElement } from '../src/utils.js';
11
+
12
+ // ============================ MODULE STATE ===============================
13
+
14
+ /**
15
+ * @type {function(): void}
16
+ * Page-wide initialization step / strategy
17
+ */
18
+ let onModuleInit = () => {};
19
+
20
+ /**
21
+ * @type {function(Object): void}
22
+ * Bid response mutation step / strategy.
23
+ */
24
+ let onBidResponse = () => {};
25
+
26
+ /**
27
+ * @type {number}
28
+ * 0 for unknown, 1 for preloaded, -1 for error.
29
+ */
30
+ let preloadStatus = 0;
31
+
32
+ // ============================ MODULE LOGIC ===============================
33
+
34
+ /**
35
+ * Page initialization step which just preloads the script, to be available whenever we start processing the bids.
36
+ * @param {string} scriptURL The script URL to preload
37
+ */
38
+ function pageInitStepPreloadScript(scriptURL) {
39
+ const linkElement = document.createElement('link');
40
+ linkElement.rel = 'preload';
41
+ linkElement.as = 'script';
42
+ linkElement.href = scriptURL;
43
+ linkElement.onload = () => { preloadStatus = 1; };
44
+ linkElement.onerror = () => { preloadStatus = -1; };
45
+ insertElement(linkElement);
46
+ }
47
+
48
+ /**
49
+ * Page initialization step which adds the protector script to the whole page. With that, there is no need wrapping bids, and the coverage is better.
50
+ * @param {string} scriptURL The script URL to add to the page for protection
51
+ */
52
+ function pageInitStepProtectPage(scriptURL) {
53
+ const scriptElement = document.createElement('script');
54
+ scriptElement.type = 'text/javascript';
55
+ scriptElement.src = scriptURL;
56
+ insertElement(scriptElement);
57
+ }
58
+
59
+ /**
60
+ * Bid processing step which alters the ad HTML to contain bid-specific information, which can be used to identify the creative later.
61
+ * @param {Object} bidResponse Bid response data
62
+ */
63
+ function bidWrapStepAugmentHtml(bidResponse) {
64
+ bidResponse.ad = `<!-- pbad://creativeId=${bidResponse.creativeId || ''}&bidderCode=${bidResponse.bidderCode || ''}&cpm=${bidResponse.cpm || ''} -->\n${bidResponse.ad}`;
65
+ }
66
+
67
+ /**
68
+ * Bid processing step which applies creative protection by wrapping the ad HTML.
69
+ * @param {string} scriptURL
70
+ * @param {number} requiredPreload
71
+ * @param {Object} bidResponse
72
+ */
73
+ function bidWrapStepProtectByWrapping(scriptURL, requiredPreload, bidResponse) {
74
+ // Still prepend bid info, it's always helpful to have creative data in its payload
75
+ bidWrapStepAugmentHtml(bidResponse);
76
+
77
+ // If preloading failed, or if configuration requires us to finish preloading -
78
+ // we should not process this bid any further
79
+ if (preloadStatus < requiredPreload) {
80
+ return;
81
+ }
82
+
83
+ const sid = generateUUID();
84
+ bidResponse.ad = `
85
+ <script type="text/javascript"
86
+ src="${scriptURL}"
87
+ data-api-integration-mode="prebid"
88
+ data-api-session-uuid="${sid}">
89
+ </script>
90
+ <script type="text/javascript">
91
+ var ad = "${encodeURIComponent(bidResponse.ad)}";
92
+ var agent = window["${sid}"];
93
+ if (agent && typeof agent.put === "function") {
94
+ agent.put(ad);
95
+ }
96
+ else {
97
+ document.open();
98
+ document.write(decodeURIComponent(ad));
99
+ document.close();
100
+ }
101
+ </script>
102
+ `;
103
+ }
104
+
105
+ /**
106
+ * Custom error class to differentiate validation errors
107
+ */
108
+ class ConfigError extends Error { }
109
+
110
+ /**
111
+ * The function to be called upon module init. Depending on the passed config, initializes properly init/bid steps or throws ConfigError.
112
+ * @param {Object} config
113
+ */
114
+ function readConfig(config) {
115
+ if (!config.params) {
116
+ throw new ConfigError('Missing config parameters for clean.io RTD module provider.');
117
+ }
118
+
119
+ if (typeof config.params.cdnUrl !== 'string' || !/^https?:\/\//.test(config.params.cdnUrl)) {
120
+ throw new ConfigError('Parameter "cdnUrl" is a required string parameter, which should start with "http(s)://".');
121
+ }
122
+
123
+ if (typeof config.params.protectionMode !== 'string') {
124
+ throw new ConfigError('Parameter "protectionMode" is a required string parameter.');
125
+ }
126
+
127
+ const scriptURL = config.params.cdnUrl;
128
+
129
+ switch (config.params.protectionMode) {
130
+ case 'full':
131
+ onModuleInit = () => pageInitStepProtectPage(scriptURL);
132
+ onBidResponse = (bidResponse) => bidWrapStepAugmentHtml(bidResponse);
133
+ break;
134
+
135
+ case 'bids':
136
+ onModuleInit = () => pageInitStepPreloadScript(scriptURL);
137
+ onBidResponse = (bidResponse) => bidWrapStepProtectByWrapping(scriptURL, 0, bidResponse);
138
+ break;
139
+
140
+ case 'bids-nowait':
141
+ onModuleInit = () => pageInitStepPreloadScript(scriptURL);
142
+ onBidResponse = (bidResponse) => bidWrapStepProtectByWrapping(scriptURL, 1, bidResponse);
143
+ break;
144
+
145
+ default:
146
+ throw new ConfigError('Parameter "protectionMode" must be one of "full" | "bids" | "bids-nowait".');
147
+ }
148
+ }
149
+
150
+ // ============================ MODULE REGISTRATION ===============================
151
+
152
+ /**
153
+ * The function which performs submodule registration.
154
+ */
155
+ function beforeInit() {
156
+ submodule('realTimeData', /** @type {RtdSubmodule} */ ({
157
+ name: 'clean.io',
158
+
159
+ init: (config, userConsent) => {
160
+ try {
161
+ readConfig(config);
162
+ onModuleInit();
163
+ return true;
164
+ } catch (err) {
165
+ if (err instanceof ConfigError) {
166
+ logError(err.message);
167
+ }
168
+ return false;
169
+ }
170
+ },
171
+
172
+ onBidResponseEvent: (bidResponse, config, userConsent) => {
173
+ onBidResponse(bidResponse);
174
+ }
175
+ }));
176
+ }
177
+
178
+ /**
179
+ * Exporting local (and otherwise encapsulated to this module) functions
180
+ * for testing purposes
181
+ */
182
+ export const __TEST__ = {
183
+ pageInitStepPreloadScript,
184
+ pageInitStepProtectPage,
185
+ bidWrapStepAugmentHtml,
186
+ bidWrapStepProtectByWrapping,
187
+ ConfigError,
188
+ readConfig,
189
+ beforeInit,
190
+ }
191
+
192
+ beforeInit();
@@ -0,0 +1,59 @@
1
+ # Overview
2
+
3
+ ```
4
+ Module Name: clean.io Rtd provider
5
+ Module Type: Rtd Provider
6
+ Maintainer: nick@clean.io
7
+ ```
8
+
9
+ The clean.io Realtime module provides effective anti-malvertising solution for publishers, including, but not limited to,
10
+ blocking unwanted 0- and 1-click redirects, deceptive ads or those with malicious landing pages, and various types of affiliate fraud.
11
+
12
+ Using this module requires prior agreement with [clean.io](https://clean.io) to obtain the necessary distribution key.
13
+
14
+
15
+ # Integration
16
+
17
+ clean.io Realtime module can be built just like any other prebid module:
18
+
19
+ ```
20
+ gulp build --modules=cleanioRtdProvider,...
21
+ ```
22
+
23
+
24
+ # Configuration
25
+
26
+ When built into prebid.js, this module can be configured through the following `pbjs.setConfig` call:
27
+
28
+ ```javascript
29
+ pbjs.setConfig({
30
+ realTimeData: {
31
+ dataProviders: [{
32
+ name: 'clean.io',
33
+ params: {
34
+ cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', ///< Contact clean.io to get your own CDN URL
35
+ protectionMode: 'full', ///< Supported modes are 'full', 'bids' and 'bids-nowait', see below.
36
+ }
37
+ }]
38
+ }
39
+ });
40
+ ```
41
+
42
+
43
+ ## Configuration parameters
44
+
45
+ {: .table .table-bordered .table-striped }
46
+ | Name | Type | Scope | Description |
47
+ | :------------ | :------------ | :------------ |:------------ |
48
+ | ``cdnUrl`` | ``string`` | Required | CDN URL of the script, which is to be used for protection. |
49
+ | ``protectionMode`` | ``'full' \| 'bids' \| 'bids-nowait'`` | Required | Integration mode. Please refer to the "Integration modes" section for details. |
50
+
51
+
52
+ ## Integration modes
53
+
54
+ {: .table .table-bordered .table-striped }
55
+ | Integration Mode | Parameter Value | Description |
56
+ | :------------ | :------------ | :------------ |
57
+ | Full page protection | ``'full'`` | Preferred mode. The module will add the protector agent script directly to the page, and it will protect all placements. This mode will make the most out of various behavioral detection mechanisms, and will also prevent typical malicious behaviors. Please note that in this mode, depending on Prebid library naming, Chrome may mistakenly tag non-ad-related content as ads: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/ad_tagging.md. |
58
+ | Bids-only protection | ``'bids'`` | The module will protect specific bid responses, more specifically, the HTML representing ad payload, by wrapping it into the agent script. Please note that in this mode, ads delivered directly, outside of Prebid integration, will not be protected, since the module can only access the ads coming through Prebid. |
59
+ | Bids-only protection with no delay on bid rendering | ``'bids-nowait'`` | Same as above, but in this mode, the script will also *not* wrap those bid responses, which arrived prior to successful preloading of agent script. |