prebid.js 7.29.0 → 7.30.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 (154) hide show
  1. package/dist/33acrossBidAdapter.js +1 -1
  2. package/dist/33acrossIdSystem.js +1 -1
  3. package/dist/adagioBidAdapter.js +1 -1
  4. package/dist/adbookpspBidAdapter.js +1 -1
  5. package/dist/adgenerationBidAdapter.js +1 -1
  6. package/dist/adrelevantisBidAdapter.js +1 -1
  7. package/dist/adtrgtmeBidAdapter.js +1 -1
  8. package/dist/adxcgBidAdapter.js +1 -1
  9. package/dist/ajaBidAdapter.js +1 -1
  10. package/dist/amxBidAdapter.js +1 -1
  11. package/dist/amxIdSystem.js +1 -1
  12. package/dist/appierAnalyticsAdapter.js +1 -1
  13. package/dist/appnexusBidAdapter.js +1 -1
  14. package/dist/asoBidAdapter.js +1 -1
  15. package/dist/axonixBidAdapter.js +1 -1
  16. package/dist/bidViewability.js +1 -1
  17. package/dist/bidglassBidAdapter.js +1 -1
  18. package/dist/big-richmediaBidAdapter.js +1 -1
  19. package/dist/bridgewellBidAdapter.js +1 -1
  20. package/dist/brightMountainMediaBidAdapter.js +1 -1
  21. package/dist/byDataAnalyticsAdapter.js +1 -1
  22. package/dist/carodaBidAdapter.js +1 -1
  23. package/dist/ccxBidAdapter.js +1 -1
  24. package/dist/chtnwBidAdapter.js +1 -1
  25. package/dist/colossussspBidAdapter.js +1 -1
  26. package/dist/concertBidAdapter.js +1 -1
  27. package/dist/connectadBidAdapter.js +1 -1
  28. package/dist/consentManagement.js +1 -1
  29. package/dist/consentManagementGpp.js +1 -0
  30. package/dist/consumableBidAdapter.js +1 -1
  31. package/dist/conversantBidAdapter.js +1 -1
  32. package/dist/craftBidAdapter.js +1 -1
  33. package/dist/criteoBidAdapter.js +1 -1
  34. package/dist/dfpAdServerVideo.js +1 -1
  35. package/dist/dspxBidAdapter.js +1 -1
  36. package/dist/eplanningBidAdapter.js +1 -1
  37. package/dist/finativeBidAdapter.js +1 -1
  38. package/dist/glimpseBidAdapter.js +1 -1
  39. package/dist/gmosspBidAdapter.js +1 -1
  40. package/dist/goldbachBidAdapter.js +1 -1
  41. package/dist/gridBidAdapter.js +1 -1
  42. package/dist/gridNMBidAdapter.js +1 -1
  43. package/dist/gumgumBidAdapter.js +1 -1
  44. package/dist/h12mediaBidAdapter.js +1 -1
  45. package/dist/id5IdSystem.js +1 -1
  46. package/dist/improvedigitalBidAdapter.js +1 -1
  47. package/dist/inmarBidAdapter.js +1 -1
  48. package/dist/insticatorBidAdapter.js +1 -1
  49. package/dist/ixBidAdapter.js +1 -1
  50. package/dist/justpremiumBidAdapter.js +1 -1
  51. package/dist/konduitAnalyticsAdapter.js +1 -1
  52. package/dist/kueezBidAdapter.js +1 -1
  53. package/dist/kueezRtbBidAdapter.js +1 -1
  54. package/dist/lassoBidAdapter.js +1 -1
  55. package/dist/lifestreetBidAdapter.js +1 -1
  56. package/dist/liveyieldAnalyticsAdapter.js +1 -1
  57. package/dist/logicadBidAdapter.js +1 -1
  58. package/dist/loglyliftBidAdapter.js +1 -1
  59. package/dist/magniteAnalyticsAdapter.js +1 -1
  60. package/dist/malltvAnalyticsAdapter.js +1 -1
  61. package/dist/marsmediaBidAdapter.js +1 -1
  62. package/dist/mediafuseBidAdapter.js +1 -1
  63. package/dist/mediasquareBidAdapter.js +1 -1
  64. package/dist/mgidBidAdapter.js +1 -1
  65. package/dist/minutemediaBidAdapter.js +1 -1
  66. package/dist/nexx360BidAdapter.js +1 -1
  67. package/dist/not-for-prod/prebid.js +122 -120
  68. package/dist/oguryBidAdapter.js +1 -1
  69. package/dist/onetagBidAdapter.js +1 -1
  70. package/dist/ooloAnalyticsAdapter.js +1 -1
  71. package/dist/outbrainBidAdapter.js +1 -1
  72. package/dist/parrableIdSystem.js +1 -1
  73. package/dist/pixfutureBidAdapter.js +1 -1
  74. package/dist/prebid-core.js +1 -1
  75. package/dist/prebidServerBidAdapter.js +1 -1
  76. package/dist/publinkIdSystem.js +1 -1
  77. package/dist/pubmaticBidAdapter.js +1 -1
  78. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  79. package/dist/pxyzBidAdapter.js +1 -1
  80. package/dist/quantcastBidAdapter.js +1 -1
  81. package/dist/readpeakBidAdapter.js +1 -1
  82. package/dist/relaidoBidAdapter.js +1 -1
  83. package/dist/rhythmoneBidAdapter.js +1 -1
  84. package/dist/riseBidAdapter.js +1 -1
  85. package/dist/rtdModule.js +1 -1
  86. package/dist/rubiconAnalyticsAdapter.js +1 -1
  87. package/dist/rubiconBidAdapter.js +1 -1
  88. package/dist/seedingAllianceBidAdapter.js +1 -1
  89. package/dist/seedtagBidAdapter.js +1 -1
  90. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  91. package/dist/sharethroughBidAdapter.js +1 -1
  92. package/dist/shinezBidAdapter.js +1 -1
  93. package/dist/smaatoBidAdapter.js +1 -1
  94. package/dist/smartadserverBidAdapter.js +1 -1
  95. package/dist/smartxBidAdapter.js +1 -1
  96. package/dist/smilewantedBidAdapter.js +1 -1
  97. package/dist/sonobiBidAdapter.js +1 -1
  98. package/dist/sovrnAnalyticsAdapter.js +1 -1
  99. package/dist/sovrnBidAdapter.js +1 -1
  100. package/dist/sspBCBidAdapter.js +1 -1
  101. package/dist/sublimeBidAdapter.js +1 -1
  102. package/dist/synacormediaBidAdapter.js +1 -1
  103. package/dist/targetVideoBidAdapter.js +1 -1
  104. package/dist/teadsBidAdapter.js +1 -1
  105. package/dist/trionBidAdapter.js +1 -1
  106. package/dist/tripleliftBidAdapter.js +1 -1
  107. package/dist/ttdBidAdapter.js +1 -1
  108. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  109. package/dist/uid2IdSystem.js +1 -1
  110. package/dist/underdogmediaBidAdapter.js +1 -1
  111. package/dist/undertoneBidAdapter.js +1 -1
  112. package/dist/vidazooBidAdapter.js +1 -1
  113. package/dist/videobyteBidAdapter.js +1 -1
  114. package/dist/viouslyBidAdapter.js +1 -0
  115. package/dist/visxBidAdapter.js +1 -1
  116. package/dist/vuukleBidAdapter.js +1 -1
  117. package/dist/widespaceBidAdapter.js +1 -1
  118. package/dist/winrBidAdapter.js +1 -1
  119. package/dist/yahoosspBidAdapter.js +1 -1
  120. package/dist/yieldmoBidAdapter.js +1 -1
  121. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  122. package/integrationExamples/gpt/gpp_us_hello_world.html +124 -0
  123. package/integrationExamples/gpt/gpp_us_hello_world_iframe.html +12 -0
  124. package/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html +140 -0
  125. package/karma.conf.maker.js +1 -1
  126. package/modules/appnexusBidAdapter.js +23 -2
  127. package/modules/bidViewability.js +6 -1
  128. package/modules/ccxBidAdapter.js +3 -2
  129. package/modules/colossussspBidAdapter.js +8 -0
  130. package/modules/consentManagement.js +4 -4
  131. package/modules/consentManagementGpp.js +386 -0
  132. package/modules/dfpAdServerVideo.js +6 -1
  133. package/modules/gridBidAdapter.js +1 -2
  134. package/modules/lassoBidAdapter.js +2 -2
  135. package/modules/magniteAnalyticsAdapter.js +9 -1
  136. package/modules/onetagBidAdapter.js +20 -29
  137. package/modules/prebidServerBidAdapter/index.js +19 -8
  138. package/modules/rtdModule/index.js +2 -1
  139. package/modules/viouslyBidAdapter.js +209 -0
  140. package/modules/viouslyBidAdapter.md +35 -0
  141. package/package.json +1 -1
  142. package/src/adapterManager.js +11 -10
  143. package/src/adapters/bidderFactory.js +5 -5
  144. package/src/consentHandler.js +11 -0
  145. package/src/prebid.js +2 -1
  146. package/test/helpers/hookSetup.js +5 -0
  147. package/test/spec/modules/ViouslyBidAdapter_spec.js +378 -0
  148. package/test/spec/modules/appnexusBidAdapter_spec.js +46 -0
  149. package/test/spec/modules/colossussspBidAdapter_spec.js +89 -2
  150. package/test/spec/modules/consentManagementGpp_spec.js +577 -0
  151. package/test/spec/modules/gridBidAdapter_spec.js +0 -18
  152. package/test/spec/modules/magniteAnalyticsAdapter_spec.js +36 -14
  153. package/test/spec/modules/onetagBidAdapter_spec.js +29 -23
  154. package/test/spec/modules/topicsFpdModule_spec.js +41 -22
@@ -0,0 +1,386 @@
1
+ /**
2
+ * This module adds GPP consentManagement support to prebid.js. It interacts with
3
+ * supported CMPs (Consent Management Platforms) to grab the user's consent information
4
+ * and make it available for any GPP supported adapters to read/pass this information to
5
+ * their system and for various other features/modules in Prebid.js.
6
+ */
7
+ import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js';
8
+ import {config} from '../src/config.js';
9
+ import {gppDataHandler} from '../src/adapterManager.js';
10
+ import {includes} from '../src/polyfill.js';
11
+ import {timedAuctionHook} from '../src/utils/perfMetrics.js';
12
+ import { enrichFPD } from '../src/fpd/enrichment.js';
13
+
14
+ const DEFAULT_CMP = 'iab';
15
+ const DEFAULT_CONSENT_TIMEOUT = 10000;
16
+ const CMP_VERSION = 1;
17
+
18
+ export let userCMP;
19
+ export let consentTimeout;
20
+ export let staticConsentData;
21
+
22
+ let consentData;
23
+ let addedConsentHook = false;
24
+
25
+ // add new CMPs here, with their dedicated lookup function
26
+ const cmpCallMap = {
27
+ 'iab': lookupIabConsent,
28
+ 'static': lookupStaticConsentData
29
+ };
30
+
31
+ /**
32
+ * This function checks the state of the IAB gppData's applicableSection field (to ensure it's populated and has a valid value).
33
+ * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section.
34
+ *
35
+ * TODO --- The initial version of the GPP CMP API spec used this naming convention, but it was later changed as an update to the spec.
36
+ * CMPs should adjust their logic to use the new format (applicableSecctions), but that may not be the case with the initial release.
37
+ * Added support just in case for this transition period, can likely be removed at a later date...
38
+ * @param gppData represents the IAB gppData object
39
+ * @returns true|false
40
+ */
41
+ function checkApplicableSectionIsReady(gppData) {
42
+ return gppData && Array.isArray(gppData.applicableSection) && gppData.applicableSection.length > 0 && gppData.applicableSection[0] !== 0;
43
+ }
44
+
45
+ /**
46
+ * This function checks the state of the IAB gppData's applicableSections field (to ensure it's populated and has a valid value).
47
+ * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section.
48
+ * @param gppData represents the IAB gppData object
49
+ * @returns true|false
50
+ */
51
+ function checkApplicableSectionsIsReady(gppData) {
52
+ return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0;
53
+ }
54
+
55
+ /**
56
+ * This function reads the consent string from the config to obtain the consent information of the user.
57
+ * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP
58
+ */
59
+ function lookupStaticConsentData({onSuccess, onError}) {
60
+ processCmpData(staticConsentData, {onSuccess, onError});
61
+ }
62
+
63
+ /**
64
+ * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user.
65
+ * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function
66
+ * based on the appropriate result.
67
+ * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP
68
+ * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging)
69
+ */
70
+ function lookupIabConsent({onSuccess, onError}) {
71
+ const cmpApiName = '__gpp';
72
+ const cmpCallbacks = {};
73
+ let registeredPostMessageResponseListener = false;
74
+
75
+ function findCMP() {
76
+ let f = window;
77
+ let cmpFrame;
78
+ let cmpDirectAccess = false;
79
+ while (true) {
80
+ try {
81
+ if (typeof f[cmpApiName] === 'function') {
82
+ cmpFrame = f;
83
+ cmpDirectAccess = true;
84
+ break;
85
+ }
86
+ } catch (e) {}
87
+
88
+ // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env
89
+ try {
90
+ if (f.frames['__gppLocator']) {
91
+ cmpFrame = f;
92
+ break;
93
+ }
94
+ } catch (e) {}
95
+
96
+ if (f === window.top) break;
97
+ f = f.parent;
98
+ }
99
+
100
+ return {
101
+ cmpFrame,
102
+ cmpDirectAccess
103
+ };
104
+ }
105
+
106
+ const {cmpFrame, cmpDirectAccess} = findCMP();
107
+
108
+ if (!cmpFrame) {
109
+ return onError('GPP CMP not found.');
110
+ }
111
+
112
+ const invokeCMP = (cmpDirectAccess) ? invokeCMPDirect : invokeCMPFrame;
113
+
114
+ function invokeCMPDirect({command, callback, parameter, version = CMP_VERSION}, resultCb) {
115
+ if (typeof resultCb === 'function') {
116
+ resultCb(cmpFrame[cmpApiName](command, callback, parameter, version));
117
+ } else {
118
+ cmpFrame[cmpApiName](command, callback, parameter, version);
119
+ }
120
+ }
121
+
122
+ function invokeCMPFrame({command, callback, parameter, version = CMP_VERSION}, resultCb) {
123
+ const callName = `${cmpApiName}Call`;
124
+ if (!registeredPostMessageResponseListener) {
125
+ // when we get the return message, call the stashed callback;
126
+ window.addEventListener('message', readPostMessageResponse, false);
127
+ registeredPostMessageResponseListener = true;
128
+ }
129
+
130
+ // call CMP via postMessage
131
+ const callId = Math.random().toString();
132
+ const msg = {
133
+ [callName]: {
134
+ command: command,
135
+ parameter,
136
+ version,
137
+ callId: callId
138
+ }
139
+ };
140
+
141
+ // TODO? - add logic to check if random was already used in the same session, and roll another if so?
142
+ cmpCallbacks[callId] = (typeof callback === 'function') ? callback : resultCb;
143
+ cmpFrame.postMessage(msg, '*');
144
+
145
+ function readPostMessageResponse(event) {
146
+ const cmpDataPkgName = `${cmpApiName}Return`;
147
+ const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data;
148
+ if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) {
149
+ const payload = json[cmpDataPkgName];
150
+
151
+ if (cmpCallbacks.hasOwnProperty(payload.callId)) {
152
+ cmpCallbacks[payload.callId](payload.returnValue);
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ const startupMsg = (cmpDirectAccess) ? 'Detected GPP CMP API is directly accessible, calling it now...'
159
+ : 'Detected GPP CMP is outside the current iframe where Prebid.js is located, calling it now...';
160
+ logInfo(startupMsg);
161
+
162
+ invokeCMP({
163
+ command: 'addEventListener',
164
+ callback: function (evt) {
165
+ if (evt) {
166
+ logInfo(`Received a ${(cmpDirectAccess ? 'direct' : 'postmsg')} response from GPP CMP for event`, evt);
167
+ if (evt.eventName === 'sectionChange' || evt.pingData.cmpStatus === 'loaded') {
168
+ invokeCMP({command: 'getGPPData'}, function (gppData) {
169
+ logInfo(`Received a ${cmpDirectAccess ? 'direct' : 'postmsg'} response from GPP CMP for getGPPData`, gppData);
170
+ processCmpData(gppData, {onSuccess, onError});
171
+ });
172
+ } else if (evt.pingData.cmpStatus === 'error') {
173
+ onError('CMP returned with a cmpStatus:error response. Please check CMP setup.');
174
+ }
175
+ }
176
+ }
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler.
182
+ *
183
+ * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra
184
+ * error arguments that will be undefined if there's no error.
185
+ */
186
+ function loadConsentData(cb) {
187
+ let isDone = false;
188
+ let timer = null;
189
+
190
+ function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) {
191
+ if (timer != null) {
192
+ clearTimeout(timer);
193
+ }
194
+ isDone = true;
195
+ gppDataHandler.setConsentData(consentData);
196
+ if (typeof cb === 'function') {
197
+ cb(shouldCancelAuction, errMsg, ...extraArgs);
198
+ }
199
+ }
200
+
201
+ if (!includes(Object.keys(cmpCallMap), userCMP)) {
202
+ done(null, false, `GPP CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`);
203
+ return;
204
+ }
205
+
206
+ const callbacks = {
207
+ onSuccess: (data) => done(data, false),
208
+ onError: function (msg, ...extraArgs) {
209
+ done(null, true, msg, ...extraArgs);
210
+ }
211
+ }
212
+ cmpCallMap[userCMP](callbacks);
213
+
214
+ if (!isDone) {
215
+ const onTimeout = () => {
216
+ const continueToAuction = (data) => {
217
+ done(data, false, 'GPP CMP did not load, continuing auction...');
218
+ }
219
+ processCmpData(consentData, {
220
+ onSuccess: continueToAuction,
221
+ onError: () => continueToAuction(storeConsentData(undefined))
222
+ })
223
+ }
224
+ if (consentTimeout === 0) {
225
+ onTimeout();
226
+ } else {
227
+ timer = setTimeout(onTimeout, consentTimeout);
228
+ }
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Like `loadConsentData`, but cache and re-use previously loaded data.
234
+ * @param cb
235
+ */
236
+ function loadIfMissing(cb) {
237
+ if (consentData) {
238
+ logInfo('User consent information already known. Pulling internally stored information...');
239
+ // eslint-disable-next-line standard/no-callback-literal
240
+ cb(false);
241
+ } else {
242
+ loadConsentData(cb);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the
248
+ * user's encoded consent string from the supported CMP. Once obtained, the module will store this
249
+ * data as part of a gppConsent object which gets transferred to adapterManager's gppDataHandler object.
250
+ * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system.
251
+ * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids.
252
+ * @param {function} fn required; The next function in the chain, used by hook.js
253
+ */
254
+ export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook(fn, reqBidsConfigObj) {
255
+ loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) {
256
+ if (errMsg) {
257
+ let log = logWarn;
258
+ if (shouldCancelAuction) {
259
+ log = logError;
260
+ errMsg = `${errMsg} Canceling auction as per consentManagement config.`;
261
+ }
262
+ log(errMsg, ...extraArgs);
263
+ }
264
+
265
+ if (shouldCancelAuction) {
266
+ fn.stopTiming();
267
+ if (typeof reqBidsConfigObj.bidsBackHandler === 'function') {
268
+ reqBidsConfigObj.bidsBackHandler();
269
+ } else {
270
+ logError('Error executing bidsBackHandler');
271
+ }
272
+ } else {
273
+ fn.call(this, reqBidsConfigObj);
274
+ }
275
+ });
276
+ });
277
+
278
+ /**
279
+ * This function checks the consent data provided by CMP to ensure it's in an expected state.
280
+ * If it's bad, we call `onError`
281
+ * If it's good, then we store the value and call `onSuccess`
282
+ */
283
+ function processCmpData(consentObject, {onSuccess, onError}) {
284
+ function checkData() {
285
+ const gppString = consentObject && consentObject.gppString;
286
+ const gppSection = (checkApplicableSectionsIsReady(consentObject)) ? consentObject.applicableSections
287
+ : (checkApplicableSectionIsReady(consentObject)) ? consentObject.applicableSection : [];
288
+
289
+ return !!(
290
+ (!Array.isArray(gppSection)) ||
291
+ (Array.isArray(gppSection) && (!gppString || !isStr(gppString)))
292
+ );
293
+ }
294
+
295
+ if (checkData()) {
296
+ onError(`CMP returned unexpected value during lookup process.`, consentObject);
297
+ } else {
298
+ onSuccess(storeConsentData(consentObject));
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction
304
+ * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only)
305
+ */
306
+ function storeConsentData(cmpConsentObject) {
307
+ consentData = {
308
+ gppString: (cmpConsentObject) ? cmpConsentObject.gppString : undefined,
309
+
310
+ fullGppData: (cmpConsentObject) || undefined,
311
+ };
312
+ consentData.applicableSections = (checkApplicableSectionsIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSections
313
+ : (checkApplicableSectionIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSection : [];
314
+ consentData.apiVersion = CMP_VERSION;
315
+ return consentData;
316
+ }
317
+
318
+ /**
319
+ * Simply resets the module's consentData variable back to undefined, mainly for testing purposes
320
+ */
321
+ export function resetConsentData() {
322
+ consentData = undefined;
323
+ userCMP = undefined;
324
+ consentTimeout = undefined;
325
+ gppDataHandler.reset();
326
+ }
327
+
328
+ /**
329
+ * A configuration function that initializes some module variables, as well as add a hook into the requestBids function
330
+ * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean)
331
+ */
332
+ export function setConsentConfig(config) {
333
+ config = config && config.gpp;
334
+ if (!config || typeof config !== 'object') {
335
+ logWarn('consentManagement.gpp config not defined, exiting consent manager module');
336
+ return;
337
+ }
338
+
339
+ if (isStr(config.cmpApi)) {
340
+ userCMP = config.cmpApi;
341
+ } else {
342
+ userCMP = DEFAULT_CMP;
343
+ logInfo(`consentManagement.gpp config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`);
344
+ }
345
+
346
+ if (isNumber(config.timeout)) {
347
+ consentTimeout = config.timeout;
348
+ } else {
349
+ consentTimeout = DEFAULT_CONSENT_TIMEOUT;
350
+ logInfo(`consentManagement.gpp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`);
351
+ }
352
+
353
+ if (userCMP === 'static') {
354
+ if (isPlainObject(config.consentData)) {
355
+ staticConsentData = config.consentData;
356
+ consentTimeout = 0;
357
+ } else {
358
+ logError(`consentManagement.gpp config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`);
359
+ }
360
+ }
361
+
362
+ logInfo('consentManagement.gpp module has been activated...');
363
+
364
+ if (!addedConsentHook) {
365
+ $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50);
366
+ }
367
+ addedConsentHook = true;
368
+ gppDataHandler.enable();
369
+ loadConsentData(); // immediately look up consent data to make it available without requiring an auction
370
+ }
371
+ config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement));
372
+
373
+ export function enrichFPDHook(next, fpd) {
374
+ return next(fpd.then(ortb2 => {
375
+ const consent = gppDataHandler.getConsentData();
376
+ if (consent) {
377
+ if (Array.isArray(consent.applicableSections)) {
378
+ deepSetValue(ortb2, 'regs.gpp_sid', consent.applicableSections);
379
+ }
380
+ deepSetValue(ortb2, 'regs.gpp', consent.gppString);
381
+ }
382
+ return ortb2;
383
+ }));
384
+ }
385
+
386
+ enrichFPD.before(enrichFPDHook);
@@ -8,7 +8,7 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui
8
8
  import { config } from '../src/config.js';
9
9
  import { getHook, submodule } from '../src/hook.js';
10
10
  import { auctionManager } from '../src/auctionManager.js';
11
- import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js';
11
+ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js';
12
12
  import * as events from '../src/events.js';
13
13
  import CONSTANTS from '../src/constants.json';
14
14
  import {getPPID} from '../src/adserver.js';
@@ -119,6 +119,11 @@ export function buildDfpVideoUrl(options) {
119
119
  const uspConsent = uspDataHandler.getConsentData();
120
120
  if (uspConsent) { queryParams.us_privacy = uspConsent; }
121
121
 
122
+ const gppConsent = gppDataHandler.getConsentData();
123
+ if (gppConsent) {
124
+ // TODO - need to know what to set here for queryParams...
125
+ }
126
+
122
127
  if (!queryParams.ppid) {
123
128
  const ppid = getPPID();
124
129
  if (ppid != null) {
@@ -94,8 +94,7 @@ export const spec = {
94
94
  let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {};
95
95
 
96
96
  const referer = refererInfo ? encodeURIComponent(refererInfo.page) : '';
97
- const bidderTimeout = config.getConfig('bidderTimeout') || timeout;
98
- const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout;
97
+ const tmax = timeout || config.getConfig('bidderTimeout');
99
98
  const imp = [];
100
99
  const bidsMap = {};
101
100
  const requests = [];
@@ -40,14 +40,14 @@ export const spec = {
40
40
  auctionId: bidRequest.auctionId,
41
41
  bidId: bidRequest.bidId,
42
42
  transactionId: bidRequest.transactionId,
43
- device: JSON.stringify(getDeviceData()),
43
+ device: encodeURIComponent(JSON.stringify(getDeviceData())),
44
44
  sizes,
45
45
  aimXR,
46
46
  uid: '$UID',
47
47
  params: JSON.stringify(bidRequest.params),
48
48
  crumbs: JSON.stringify(bidRequest.crumbs),
49
49
  prebidVersion: '$prebid.version$',
50
- version: 2,
50
+ version: 3,
51
51
  coppa: config.getConfig('coppa') == true ? 1 : 0,
52
52
  ccpa: bidderRequest.uspConsent || undefined
53
53
  }
@@ -643,7 +643,15 @@ magniteAdapter.disableAnalytics = function () {
643
643
  accountId = undefined;
644
644
  resetConfs();
645
645
  magniteAdapter.originDisableAnalytics();
646
- }
646
+ };
647
+
648
+ magniteAdapter.onDataDeletionRequest = function () {
649
+ if (storage.localStorageIsEnabled()) {
650
+ storage.removeDataFromLocalStorage(COOKIE_NAME);
651
+ } else {
652
+ throw Error('Unable to access local storage, no data deleted');
653
+ }
654
+ };
647
655
 
648
656
  magniteAdapter.MODULE_INITIALIZED_TIME = Date.now();
649
657
  magniteAdapter.referrerHostname = '';
@@ -3,7 +3,7 @@
3
3
  import { BANNER, VIDEO } from '../src/mediaTypes.js';
4
4
  import { INSTREAM, OUTSTREAM } from '../src/video.js';
5
5
  import { Renderer } from '../src/Renderer.js';
6
- import {find} from '../src/polyfill.js';
6
+ import { find } from '../src/polyfill.js';
7
7
  import { getStorageManager } from '../src/storageManager.js';
8
8
  import { registerBidder } from '../src/adapters/bidderFactory.js';
9
9
  import { deepClone, logError, deepAccess } from '../src/utils.js';
@@ -13,7 +13,7 @@ const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/';
13
13
  const BIDDER_CODE = 'onetag';
14
14
  const GVLID = 241;
15
15
 
16
- const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
16
+ const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE });
17
17
 
18
18
  /**
19
19
  * Determines whether or not the given bid request is valid.
@@ -53,7 +53,7 @@ export function isValid(type, bid) {
53
53
  function buildRequests(validBidRequests, bidderRequest) {
54
54
  const payload = {
55
55
  bids: requestsToBids(validBidRequests),
56
- ...getPageInfo()
56
+ ...getPageInfo(bidderRequest)
57
57
  };
58
58
  if (bidderRequest && bidderRequest.gdprConsent) {
59
59
  payload.gdprConsent = {
@@ -74,7 +74,10 @@ function buildRequests(validBidRequests, bidderRequest) {
74
74
  if (storage.hasLocalStorage()) {
75
75
  payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid');
76
76
  }
77
- } catch (e) {}
77
+ } catch (e) { }
78
+ const connection = navigator.connection || navigator.webkitConnection;
79
+ payload.networkConnectionType = (connection && connection.type) ? connection.type : null;
80
+ payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null;
78
81
  return {
79
82
  method: 'POST',
80
83
  url: ENDPOINT,
@@ -112,7 +115,7 @@ function interpretResponse(serverResponse, bidderRequest) {
112
115
  if (bid.mediaType === BANNER) {
113
116
  responseBid.ad = bid.ad;
114
117
  } else if (bid.mediaType === VIDEO) {
115
- const {context, adUnitCode} = find(requestData.bids, (item) =>
118
+ const { context, adUnitCode } = find(requestData.bids, (item) =>
116
119
  item.bidId === bid.requestId &&
117
120
  item.type === VIDEO
118
121
  );
@@ -141,7 +144,7 @@ function createRenderer(bid, rendererOptions = {}) {
141
144
  loaded: false
142
145
  });
143
146
  try {
144
- renderer.setRender(({renderer, width, height, vastXml, adUnitCode}) => {
147
+ renderer.setRender(({ renderer, width, height, vastXml, adUnitCode }) => {
145
148
  renderer.push(() => {
146
149
  window.onetag.Player.init({
147
150
  ...bid,
@@ -162,7 +165,6 @@ function createRenderer(bid, rendererOptions = {}) {
162
165
  function getFrameNesting() {
163
166
  let topmostFrame = window;
164
167
  let parent = window.parent;
165
- let currentFrameNesting = 0;
166
168
  try {
167
169
  while (topmostFrame !== topmostFrame.parent) {
168
170
  parent = topmostFrame.parent;
@@ -170,13 +172,8 @@ function getFrameNesting() {
170
172
  parent.location.href;
171
173
  topmostFrame = topmostFrame.parent;
172
174
  }
173
- } catch (e) {
174
- currentFrameNesting = parent === topmostFrame.top ? 1 : 2;
175
- }
176
- return {
177
- topmostFrame,
178
- currentFrameNesting
179
- }
175
+ } catch (e) { }
176
+ return topmostFrame;
180
177
  }
181
178
 
182
179
  function getDocumentVisibility(window) {
@@ -197,21 +194,15 @@ function getDocumentVisibility(window) {
197
194
 
198
195
  /**
199
196
  * Returns information about the page needed by the server in an object to be converted in JSON
200
- * @returns {{location: *, referrer: (*|string), masked: *, wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}}
197
+ * @returns {{location: *, referrer: (*|string), stack: (*|Array.<String>), numIframes: (*|Number), wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}}
201
198
  */
202
- function getPageInfo() {
203
- const { topmostFrame, currentFrameNesting } = getFrameNesting();
199
+ function getPageInfo(bidderRequest) {
200
+ const topmostFrame = getFrameNesting();
204
201
  return {
205
- location: topmostFrame.location.href,
206
- referrer:
207
- topmostFrame.document.referrer !== ''
208
- ? topmostFrame.document.referrer
209
- : null,
210
- ancestorOrigin:
211
- window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0
212
- ? window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1]
213
- : null,
214
- masked: currentFrameNesting,
202
+ location: deepAccess(bidderRequest, 'refererInfo.page', null),
203
+ referrer: deepAccess(bidderRequest, 'refererInfo.ref', null),
204
+ stack: deepAccess(bidderRequest, 'refererInfo.stack', []),
205
+ numIframes: deepAccess(bidderRequest, 'refererInfo.numIframes', 0),
215
206
  wWidth: topmostFrame.innerWidth,
216
207
  wHeight: topmostFrame.innerHeight,
217
208
  oWidth: topmostFrame.outerWidth,
@@ -230,7 +221,7 @@ function getPageInfo() {
230
221
  timing: getTiming(),
231
222
  version: {
232
223
  prebid: '$prebid.version$',
233
- adapter: '1.1.0'
224
+ adapter: '1.1.1'
234
225
  }
235
226
  };
236
227
  }
@@ -344,7 +335,7 @@ function getSizes(sizes) {
344
335
  const ret = [];
345
336
  for (let i = 0; i < sizes.length; i++) {
346
337
  const size = sizes[i];
347
- ret.push({width: size[0], height: size[1]})
338
+ ret.push({ width: size[0], height: size[1] })
348
339
  }
349
340
  return ret;
350
341
  }
@@ -216,7 +216,7 @@ export function resetSyncedStatus() {
216
216
  /**
217
217
  * @param {Array} bidderCodes list of bidders to request user syncs for.
218
218
  */
219
- function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) {
219
+ function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig) {
220
220
  if (_s2sConfigs.length === _syncCount) {
221
221
  return;
222
222
  }
@@ -246,6 +246,15 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) {
246
246
  payload.us_privacy = uspConsent;
247
247
  }
248
248
 
249
+ if (gppConsent) {
250
+ // proposing the following formatting, can adjust if needed...
251
+ // update - leaving this param as an array, since it's part of a POST payload where the [] characters shouldn't matter too much
252
+ payload.gpp_sid = gppConsent.applicableSections
253
+ // should we add check if applicableSections was not equal to -1 (where user was out of scope)?
254
+ // this would be similar to what was done above for TCF
255
+ payload.gpp = gppConsent.gppString;
256
+ }
257
+
249
258
  if (typeof s2sConfig.coopSync === 'boolean') {
250
259
  payload.coopSync = s2sConfig.coopSync;
251
260
  }
@@ -330,7 +339,7 @@ function doBidderSync(type, url, bidder, done, timeout) {
330
339
  *
331
340
  * @param {Array} bidders a list of bidder names
332
341
  */
333
- function doClientSideSyncs(bidders, gdprConsent, uspConsent) {
342
+ function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) {
334
343
  bidders.forEach(bidder => {
335
344
  let clientAdapter = adapterManager.getBidAdapter(bidder);
336
345
  if (clientAdapter && clientAdapter.registerSyncs) {
@@ -341,7 +350,8 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent) {
341
350
  clientAdapter,
342
351
  [],
343
352
  gdprConsent,
344
- uspConsent
353
+ uspConsent,
354
+ gppConsent
345
355
  )
346
356
  );
347
357
  }
@@ -411,12 +421,13 @@ function getMatchingConsentUrl(urlProp, gdprConsent) {
411
421
  }
412
422
 
413
423
  function getConsentData(bidRequests) {
414
- let gdprConsent, uspConsent;
424
+ let gdprConsent, uspConsent, gppConsent;
415
425
  if (Array.isArray(bidRequests) && bidRequests.length > 0) {
416
426
  gdprConsent = bidRequests[0].gdprConsent;
417
427
  uspConsent = bidRequests[0].uspConsent;
428
+ gppConsent = bidRequests[0].gppConsent;
418
429
  }
419
- return { gdprConsent, uspConsent };
430
+ return { gdprConsent, uspConsent, gppConsent };
420
431
  }
421
432
 
422
433
  /**
@@ -433,7 +444,7 @@ export function PrebidServer() {
433
444
  done = adapterMetrics.startTiming('total').stopBefore(done);
434
445
  bidRequests.forEach(req => useMetrics(req.metrics).join(adapterMetrics, {continuePropagation: false}));
435
446
 
436
- let { gdprConsent, uspConsent } = getConsentData(bidRequests);
447
+ let { gdprConsent, uspConsent, gppConsent } = getConsentData(bidRequests);
437
448
 
438
449
  if (Array.isArray(_s2sConfigs)) {
439
450
  if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) {
@@ -441,7 +452,7 @@ export function PrebidServer() {
441
452
  .map(bidder => adapterManager.aliasRegistry[bidder] || bidder)
442
453
  .filter((bidder, index, array) => (array.indexOf(bidder) === index));
443
454
 
444
- queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig);
455
+ queueSync(syncBidders, gdprConsent, uspConsent, gppConsent, s2sBidRequest.s2sConfig);
445
456
  }
446
457
 
447
458
  processPBSRequest(s2sBidRequest, bidRequests, ajax, {
@@ -450,7 +461,7 @@ export function PrebidServer() {
450
461
  bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest));
451
462
  }
452
463
  done();
453
- doClientSideSyncs(requestedBidders, gdprConsent, uspConsent);
464
+ doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent);
454
465
  },
455
466
  onError: done,
456
467
  onBid: function ({adUnit, bid}) {
@@ -163,7 +163,7 @@ import {getHook, module} from '../../src/hook.js';
163
163
  import {logError, logInfo, logWarn} from '../../src/utils.js';
164
164
  import * as events from '../../src/events.js';
165
165
  import CONSTANTS from '../../src/constants.json';
166
- import adapterManager, {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js';
166
+ import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js';
167
167
  import {find} from '../../src/polyfill.js';
168
168
  import {timedAuctionHook} from '../../src/utils/perfMetrics.js';
169
169
 
@@ -246,6 +246,7 @@ function getConsentData() {
246
246
  return {
247
247
  gdpr: gdprDataHandler.getConsentData(),
248
248
  usp: uspDataHandler.getConsentData(),
249
+ gpp: gppDataHandler.getConsentData(),
249
250
  coppa: !!(config.getConfig('coppa'))
250
251
  }
251
252
  }