prebid.js 7.51.0 → 7.52.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 (199) 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/adqueryBidAdapter.js +1 -1
  7. package/dist/adrelevantisBidAdapter.js +1 -1
  8. package/dist/adriverIdSystem.js +1 -1
  9. package/dist/adtrgtmeBidAdapter.js +1 -1
  10. package/dist/adxcgBidAdapter.js +1 -1
  11. package/dist/adyoulikeBidAdapter.js +1 -1
  12. package/dist/ajaBidAdapter.js +1 -1
  13. package/dist/allowActivities.js +1 -0
  14. package/dist/amxBidAdapter.js +1 -1
  15. package/dist/amxIdSystem.js +1 -1
  16. package/dist/appierAnalyticsAdapter.js +1 -1
  17. package/dist/appnexusBidAdapter.js +1 -1
  18. package/dist/asoBidAdapter.js +1 -1
  19. package/dist/axonixBidAdapter.js +1 -1
  20. package/dist/bidglassBidAdapter.js +1 -1
  21. package/dist/big-richmediaBidAdapter.js +1 -1
  22. package/dist/bridgewellBidAdapter.js +1 -1
  23. package/dist/brightMountainMediaBidAdapter.js +1 -1
  24. package/dist/carodaBidAdapter.js +1 -1
  25. package/dist/chtnwBidAdapter.js +1 -1
  26. package/dist/concertBidAdapter.js +1 -1
  27. package/dist/connectIdSystem.js +1 -1
  28. package/dist/connectadBidAdapter.js +1 -1
  29. package/dist/consumableBidAdapter.js +1 -1
  30. package/dist/conversantAnalyticsAdapter.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/cwireBidAdapter.js +1 -1
  35. package/dist/dependencies.json +6 -0
  36. package/dist/dspxBidAdapter.js +1 -1
  37. package/dist/eplanningBidAdapter.js +1 -1
  38. package/dist/feedadBidAdapter.js +1 -1
  39. package/dist/finativeBidAdapter.js +1 -1
  40. package/dist/freewheel-sspBidAdapter.js +1 -1
  41. package/dist/gdprEnforcement.js +1 -1
  42. package/dist/glimpseBidAdapter.js +1 -1
  43. package/dist/gmosspBidAdapter.js +1 -1
  44. package/dist/goldbachBidAdapter.js +1 -1
  45. package/dist/greenbidsAnalyticsAdapter.js +1 -1
  46. package/dist/greenbidsRtdProvider.js +1 -1
  47. package/dist/gridBidAdapter.js +1 -1
  48. package/dist/growthCodeRtdProvider.js +1 -0
  49. package/dist/gumgumBidAdapter.js +1 -1
  50. package/dist/h12mediaBidAdapter.js +1 -1
  51. package/dist/id5IdSystem.js +1 -1
  52. package/dist/improvedigitalBidAdapter.js +1 -1
  53. package/dist/inmarBidAdapter.js +1 -1
  54. package/dist/insticatorBidAdapter.js +1 -1
  55. package/dist/ixBidAdapter.js +1 -1
  56. package/dist/justpremiumBidAdapter.js +1 -1
  57. package/dist/kargoBidAdapter.js +1 -1
  58. package/dist/konduitAnalyticsAdapter.js +1 -1
  59. package/dist/kueezBidAdapter.js +1 -1
  60. package/dist/kueezRtbBidAdapter.js +1 -1
  61. package/dist/kulturemediaBidAdapter.js +1 -1
  62. package/dist/lassoBidAdapter.js +1 -1
  63. package/dist/lifestreetBidAdapter.js +1 -1
  64. package/dist/liveyieldAnalyticsAdapter.js +1 -1
  65. package/dist/logicadBidAdapter.js +1 -1
  66. package/dist/loglyliftBidAdapter.js +1 -1
  67. package/dist/magniteAnalyticsAdapter.js +1 -1
  68. package/dist/malltvAnalyticsAdapter.js +1 -1
  69. package/dist/marsmediaBidAdapter.js +1 -1
  70. package/dist/mediafuseBidAdapter.js +1 -1
  71. package/dist/mediasquareBidAdapter.js +1 -1
  72. package/dist/mgidBidAdapter.js +1 -1
  73. package/dist/minutemediaBidAdapter.js +1 -1
  74. package/dist/minutemediaplusBidAdapter.js +1 -1
  75. package/dist/nexx360BidAdapter.js +1 -1
  76. package/dist/not-for-prod/prebid.js +136 -132
  77. package/dist/objectGuard.js +1 -0
  78. package/dist/oguryBidAdapter.js +1 -1
  79. package/dist/onetagBidAdapter.js +1 -1
  80. package/dist/ooloAnalyticsAdapter.js +1 -1
  81. package/dist/optidigitalBidAdapter.js +1 -1
  82. package/dist/outbrainBidAdapter.js +1 -1
  83. package/dist/oxxionAnalyticsAdapter.js +1 -0
  84. package/dist/pairIdSystem.js +1 -1
  85. package/dist/parrableIdSystem.js +1 -1
  86. package/dist/pixfutureBidAdapter.js +1 -1
  87. package/dist/prebid-core.js +1 -1
  88. package/dist/publinkIdSystem.js +1 -1
  89. package/dist/pubmaticBidAdapter.js +1 -1
  90. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  91. package/dist/pxyzBidAdapter.js +1 -1
  92. package/dist/quantcastBidAdapter.js +1 -1
  93. package/dist/readpeakBidAdapter.js +1 -1
  94. package/dist/relaidoBidAdapter.js +1 -1
  95. package/dist/retailspotBidAdapter.js +1 -1
  96. package/dist/rhythmoneBidAdapter.js +1 -1
  97. package/dist/riseBidAdapter.js +1 -1
  98. package/dist/rtdModule.js +1 -1
  99. package/dist/rubiconAnalyticsAdapter.js +1 -1
  100. package/dist/rubiconBidAdapter.js +1 -1
  101. package/dist/seedingAllianceBidAdapter.js +1 -1
  102. package/dist/seedtagBidAdapter.js +1 -1
  103. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  104. package/dist/sharethroughBidAdapter.js +1 -1
  105. package/dist/shinezBidAdapter.js +1 -1
  106. package/dist/smaatoBidAdapter.js +1 -1
  107. package/dist/smartadserverBidAdapter.js +1 -1
  108. package/dist/smartxBidAdapter.js +1 -1
  109. package/dist/smilewantedBidAdapter.js +1 -1
  110. package/dist/sonobiBidAdapter.js +1 -1
  111. package/dist/sovrnAnalyticsAdapter.js +1 -1
  112. package/dist/sovrnBidAdapter.js +1 -1
  113. package/dist/sspBCBidAdapter.js +1 -1
  114. package/dist/stvBidAdapter.js +1 -1
  115. package/dist/sublimeBidAdapter.js +1 -1
  116. package/dist/synacormediaBidAdapter.js +1 -1
  117. package/dist/targetVideoBidAdapter.js +1 -1
  118. package/dist/teadsBidAdapter.js +1 -1
  119. package/dist/trionBidAdapter.js +1 -1
  120. package/dist/tripleliftBidAdapter.js +1 -1
  121. package/dist/ttdBidAdapter.js +1 -1
  122. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  123. package/dist/uid2IdSystem.js +1 -1
  124. package/dist/underdogmediaBidAdapter.js +1 -1
  125. package/dist/undertoneBidAdapter.js +1 -1
  126. package/dist/userId.js +1 -1
  127. package/dist/vidazooBidAdapter.js +1 -1
  128. package/dist/videobyteBidAdapter.js +1 -1
  129. package/dist/visxBidAdapter.js +1 -1
  130. package/dist/vuukleBidAdapter.js +1 -1
  131. package/dist/widespaceBidAdapter.js +1 -1
  132. package/dist/winrBidAdapter.js +1 -1
  133. package/dist/yahoosspBidAdapter.js +1 -1
  134. package/dist/yieldmoBidAdapter.js +1 -1
  135. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  136. package/dist/zeta_global_sspBidAdapter.js +1 -1
  137. package/integrationExamples/gpt/growthcode.html +20 -9
  138. package/libraries/objectGuard/objectGuard.js +108 -0
  139. package/libraries/objectGuard/ortbGuard.js +88 -0
  140. package/modules/adgenerationBidAdapter.js +14 -5
  141. package/modules/adriverIdSystem.js +1 -1
  142. package/modules/allowActivities.js +74 -0
  143. package/modules/connectIdSystem.js +89 -13
  144. package/modules/connectIdSystem.md +4 -7
  145. package/modules/criteoBidAdapter.js +9 -0
  146. package/modules/gdprEnforcement.js +98 -169
  147. package/modules/growthCodeRtdProvider.js +131 -0
  148. package/modules/growthCodeRtdProvider.md +55 -0
  149. package/modules/ixBidAdapter.js +5 -2
  150. package/modules/oxxionAnalyticsAdapter.js +212 -0
  151. package/modules/oxxionAnalyticsAdapter.md +33 -0
  152. package/modules/pairIdSystem.js +6 -6
  153. package/modules/rtdModule/index.js +12 -1
  154. package/modules/sharethroughBidAdapter.js +2 -2
  155. package/modules/smartadserverBidAdapter.js +5 -0
  156. package/modules/stvBidAdapter.js +34 -1
  157. package/modules/undertoneBidAdapter.js +9 -1
  158. package/modules/userId/index.js +69 -41
  159. package/modules/yahoosspBidAdapter.js +45 -3
  160. package/modules/yahoosspBidAdapter.md +1 -1
  161. package/modules/zeta_global_sspBidAdapter.js +1 -0
  162. package/package.json +1 -1
  163. package/src/activities/activities.js +47 -0
  164. package/src/activities/activityParams.js +8 -0
  165. package/src/activities/modules.js +1 -1
  166. package/src/activities/params.js +59 -0
  167. package/src/activities/redactor.js +157 -0
  168. package/src/activities/rules.js +95 -0
  169. package/src/adapterManager.js +45 -8
  170. package/src/fpd/rootDomain.js +1 -1
  171. package/src/prebid.js +1 -1
  172. package/src/storageManager.js +57 -44
  173. package/src/userSync.js +35 -18
  174. package/test/spec/activities/allowActivites_spec.js +138 -0
  175. package/test/spec/activities/objectGuard_spec.js +144 -0
  176. package/test/spec/activities/ortbGuard_spec.js +140 -0
  177. package/test/spec/activities/params_spec.js +25 -0
  178. package/test/spec/activities/redactor_spec.js +296 -0
  179. package/test/spec/activities/rules_spec.js +135 -0
  180. package/test/spec/modules/adgenerationBidAdapter_spec.js +52 -12
  181. package/test/spec/modules/connectIdSystem_spec.js +291 -23
  182. package/test/spec/modules/criteoBidAdapter_spec.js +84 -0
  183. package/test/spec/modules/gdprEnforcement_spec.js +127 -414
  184. package/test/spec/modules/growthCodeRtdProvider_spec.js +127 -0
  185. package/test/spec/modules/ixBidAdapter_spec.js +2 -1
  186. package/test/spec/modules/oxxionAnalyticsAdapter_spec.js +324 -0
  187. package/test/spec/modules/pairIdSystem_spec.js +16 -3
  188. package/test/spec/modules/realTimeDataModule_spec.js +1 -1
  189. package/test/spec/modules/sharethroughBidAdapter_spec.js +1 -1
  190. package/test/spec/modules/smartadserverBidAdapter_spec.js +42 -0
  191. package/test/spec/modules/stvBidAdapter_spec.js +13 -1
  192. package/test/spec/modules/undertoneBidAdapter_spec.js +57 -1
  193. package/test/spec/modules/userId_spec.js +80 -21
  194. package/test/spec/modules/yahoosspBidAdapter_spec.js +103 -51
  195. package/test/spec/modules/zeta_global_sspBidAdapter_spec.js +4 -0
  196. package/test/spec/unit/core/adapterManager_spec.js +181 -1
  197. package/test/spec/unit/core/storageManager_spec.js +76 -68
  198. package/test/spec/unit/pbjs_api_spec.js +15 -25
  199. package/test/spec/userSync_spec.js +45 -16
@@ -11,11 +11,16 @@ import {includes} from '../src/polyfill.js';
11
11
  import {getRefererInfo} from '../src/refererDetection.js';
12
12
  import {getStorageManager} from '../src/storageManager.js';
13
13
  import {formatQS, isPlainObject, logError, parseUrl} from '../src/utils.js';
14
- import {uspDataHandler} from '../src/adapterManager.js';
14
+ import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js';
15
15
  import {MODULE_TYPE_UID} from '../src/activities/modules.js';
16
16
 
17
17
  const MODULE_NAME = 'connectId';
18
- const STORAGE_EXPIRY_DAYS = 14;
18
+ const STORAGE_EXPIRY_DAYS = 365;
19
+ const STORAGE_DURATION = 60 * 60 * 24 * 1000 * STORAGE_EXPIRY_DAYS;
20
+ const ID_EXPIRY_DAYS = 14;
21
+ const VALID_ID_DURATION = 60 * 60 * 24 * 1000 * ID_EXPIRY_DAYS;
22
+ const PUID_EXPIRY_DAYS = 30;
23
+ const PUID_EXPIRY = 60 * 60 * 24 * 1000 * PUID_EXPIRY_DAYS;
19
24
  const VENDOR_ID = 25;
20
25
  const PLACEHOLDER = '__PIXEL_ID__';
21
26
  const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`;
@@ -28,11 +33,11 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleNam
28
33
  * @param {Object} obj
29
34
  */
30
35
  function storeObject(obj) {
31
- const expires = Date.now() + (60 * 60 * 24 * 1000 * STORAGE_EXPIRY_DAYS);
36
+ const expires = Date.now() + STORAGE_DURATION;
32
37
  if (storage.cookiesAreEnabled()) {
33
38
  setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname());
34
- } else if (storage.localStorageIsEnabled()) {
35
- obj.__expires = expires;
39
+ }
40
+ if (storage.localStorageIsEnabled()) {
36
41
  storage.setDataInLocalStorage(MODULE_NAME, JSON.stringify(obj));
37
42
  }
38
43
  }
@@ -75,7 +80,9 @@ function getIdFromLocalStorage() {
75
80
  if (storedIdData) {
76
81
  try {
77
82
  storedIdData = JSON.parse(storedIdData);
78
- } catch {}
83
+ } catch (e) {
84
+ logError(`${MODULE_NAME} module: error while reading the local storage data.`);
85
+ }
79
86
  if (isPlainObject(storedIdData) && storedIdData.__expires &&
80
87
  storedIdData.__expires <= Date.now()) {
81
88
  storage.removeDataFromLocalStorage(MODULE_NAME);
@@ -87,6 +94,34 @@ function getIdFromLocalStorage() {
87
94
  return null;
88
95
  }
89
96
 
97
+ function syncLocalStorageToCookie() {
98
+ if (!storage.cookiesAreEnabled()) {
99
+ return;
100
+ }
101
+ const value = getIdFromLocalStorage();
102
+ const newCookieExpireTime = Date.now() + STORAGE_DURATION;
103
+ setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(value), new Date(newCookieExpireTime), getSiteHostname());
104
+ }
105
+
106
+ function isStale(storedIdData) {
107
+ if (isPlainObject(storedIdData) && storedIdData.lastSynced &&
108
+ (storedIdData.lastSynced + VALID_ID_DURATION) <= Date.now()) {
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+
114
+ function getStoredId() {
115
+ let storedId = getIdFromCookie();
116
+ if (!storedId) {
117
+ storedId = getIdFromLocalStorage();
118
+ if (storedId && !isStale(storedId)) {
119
+ syncLocalStorageToCookie();
120
+ }
121
+ }
122
+ return storedId;
123
+ }
124
+
90
125
  function getSiteHostname() {
91
126
  const pageInfo = parseUrl(getRefererInfo().page);
92
127
  return pageInfo.hostname;
@@ -127,16 +162,31 @@ export const connectIdSubmodule = {
127
162
  return;
128
163
  }
129
164
  const params = config.params || {};
130
- if (!params || (typeof params.he !== 'string' && typeof params.puid !== 'string') ||
165
+ if (!params ||
131
166
  (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) {
132
- logError(`${MODULE_NAME} module: configurataion requires the 'pixelId' and at ` +
133
- `least one of the 'he' or 'puid' parameters to be defined.`);
167
+ logError(`${MODULE_NAME} module: configuration requires the 'pixelId'.`);
134
168
  return;
135
169
  }
136
170
 
137
- const storedId = getIdFromCookie() || getIdFromLocalStorage();
171
+ const storedId = getStoredId();
172
+
173
+ let shouldResync = isStale(storedId);
174
+
138
175
  if (storedId) {
139
- return {id: storedId};
176
+ if (isPlainObject(storedId) && storedId.puid && storedId.lastUsed && !params.puid &&
177
+ (storedId.lastUsed + PUID_EXPIRY) <= Date.now()) {
178
+ delete storedId.puid;
179
+ shouldResync = true;
180
+ }
181
+ if ((params.he && params.he !== storedId.he) ||
182
+ (params.puid && params.puid !== storedId.puid)) {
183
+ shouldResync = true;
184
+ }
185
+ if (!shouldResync) {
186
+ storedId.lastUsed = Date.now();
187
+ storeObject(storedId);
188
+ return {id: storedId};
189
+ }
140
190
  }
141
191
 
142
192
  const uspString = uspDataHandler.getConsentData() || '';
@@ -148,6 +198,14 @@ export const connectIdSubmodule = {
148
198
  us_privacy: uspString
149
199
  };
150
200
 
201
+ const gppConsent = gppDataHandler.getConsentData();
202
+ if (gppConsent) {
203
+ data.gpp = `${gppConsent.gppString ? gppConsent.gppString : ''}`;
204
+ if (Array.isArray(gppConsent.applicableSections)) {
205
+ data.gpp_sid = gppConsent.applicableSections.join(',');
206
+ }
207
+ }
208
+
151
209
  let topmostLocation = getRefererInfo().topmostLocation;
152
210
  if (typeof topmostLocation === 'string') {
153
211
  data.url = topmostLocation.split('?')[0];
@@ -159,6 +217,14 @@ export const connectIdSubmodule = {
159
217
  }
160
218
  });
161
219
 
220
+ const hashedEmail = params.he || storedId?.he;
221
+ if (hashedEmail) {
222
+ data.he = hashedEmail;
223
+ }
224
+ if (!data.puid && storedId?.puid) {
225
+ data.puid = storedId.puid;
226
+ }
227
+
162
228
  const resp = function (callback) {
163
229
  const callbacks = {
164
230
  success: response => {
@@ -166,7 +232,12 @@ export const connectIdSubmodule = {
166
232
  if (response) {
167
233
  try {
168
234
  responseObj = JSON.parse(response);
169
- if (isPlainObject(responseObj) && Object.keys(responseObj).length > 0) {
235
+ if (isPlainObject(responseObj) && Object.keys(responseObj).length > 0 &&
236
+ (!!responseObj.connectId || !!responseObj.connectid)) {
237
+ responseObj.he = params.he;
238
+ responseObj.puid = params.puid || responseObj.puid;
239
+ responseObj.lastSynced = Date.now();
240
+ responseObj.lastUsed = Date.now();
170
241
  storeObject(responseObj);
171
242
  } else {
172
243
  logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`);
@@ -186,7 +257,12 @@ export const connectIdSubmodule = {
186
257
  let url = `${params.endpoint || endpoint}?${formatQS(data)}`;
187
258
  connectIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true});
188
259
  };
189
- return {callback: resp};
260
+ const result = {callback: resp};
261
+ if (shouldResync && storedId) {
262
+ result.id = storedId;
263
+ }
264
+
265
+ return result;
190
266
  },
191
267
 
192
268
  /**
@@ -2,6 +2,8 @@
2
2
 
3
3
  Yahoo ConnectID user ID Module.
4
4
 
5
+ *Note: The storage config should be ommited as the module handles the storage of the needed information.
6
+
5
7
  ### Prebid Params
6
8
 
7
9
  ```
@@ -9,11 +11,6 @@ pbjs.setConfig({
9
11
  userSync: {
10
12
  userIds: [{
11
13
  name: 'connectId',
12
- storage: {
13
- name: 'connectId',
14
- type: 'html5',
15
- expires: 15
16
- },
17
14
  params: {
18
15
  pixelId: 58776,
19
16
  he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a'
@@ -31,5 +28,5 @@ The below parameters apply only to the Yahoo ConnectID user ID Module.
31
28
  | params | Required | Object | Container of all module params. ||
32
29
  | params.pixelId | Required | Number |
33
30
  The Yahoo-supplied publisher-specific pixel ID. | `"0000"` |
34
- | params.he | Optional | String | The SHA-256 hashed user email address which has been lowercased prior to hashing. Pass both `he` and `puid` params if present, otherwise pass either of the two that is available. |`"ed8ddbf5a171981db8ef938596ca297d5e3f84bcc280041c5880dba3baf9c1d4"`|
35
- | params.puid | Optional | String | The publisher supplied user identifier such as a first-party cookie. Pass both `he` and `puid` params if present, otherwise pass either of the two that is available. | `"ab9iibf5a231ii1db8ef911596ca297d5e3f84biii00041c5880dba3baf9c1da"` |
31
+ | params.he | Optional | String | The SHA-256 hashed user email address which has been lowercased prior to hashing. |`"ed8ddbf5a171981db8ef938596ca297d5e3f84bcc280041c5880dba3baf9c1d4"`|
32
+ | params.puid | Optional | String | A domain-specific user identifier such as a first-party cookie. If not passed, a puid value will be auto-generated and stored in local and / or cookie storage. | `"ab9iibf5a231ii1db8ef911596ca297d5e3f84biii00041c5880dba3baf9c1da"` |
@@ -541,6 +541,15 @@ function buildCdbRequest(context, bidRequests, bidderRequest) {
541
541
  request.user.ext = request.user.ext || {};
542
542
  request.user.ext.eids = [...userIdAsEids];
543
543
  }
544
+ if (bidderRequest && bidderRequest.ortb2?.bcat) {
545
+ request.bcat = bidderRequest.ortb2.bcat;
546
+ }
547
+ if (bidderRequest && bidderRequest.ortb2?.badv) {
548
+ request.badv = bidderRequest.ortb2.badv;
549
+ }
550
+ if (bidderRequest && bidderRequest.ortb2?.bapp) {
551
+ request.bapp = bidderRequest.ortb2.bapp;
552
+ }
544
553
  return request;
545
554
  }
546
555
 
@@ -2,30 +2,42 @@
2
2
  * This module gives publishers extra set of features to enforce individual purposes of TCF v2
3
3
  */
4
4
 
5
- import {deepAccess, hasDeviceAccess, isArray, logError, logWarn} from '../src/utils.js';
5
+ import {deepAccess, logError, logWarn} from '../src/utils.js';
6
6
  import {config} from '../src/config.js';
7
7
  import adapterManager, {gdprDataHandler} from '../src/adapterManager.js';
8
- import {find, includes} from '../src/polyfill.js';
9
- import {registerSyncInner} from '../src/adapters/bidderFactory.js';
8
+ import {find} from '../src/polyfill.js';
10
9
  import {getHook} from '../src/hook.js';
11
- import {validateStorageEnforcement} from '../src/storageManager.js';
12
10
  import * as events from '../src/events.js';
13
11
  import CONSTANTS from '../src/constants.json';
14
12
  import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js';
15
13
  import {
16
14
  MODULE_TYPE_ANALYTICS,
17
15
  MODULE_TYPE_BIDDER,
18
- MODULE_TYPE_CORE, MODULE_TYPE_RTD,
16
+ MODULE_TYPE_PREBID,
17
+ MODULE_TYPE_RTD,
19
18
  MODULE_TYPE_UID
20
19
  } from '../src/activities/modules.js';
20
+ import {
21
+ ACTIVITY_PARAM_ANL_CONFIG,
22
+ ACTIVITY_PARAM_COMPONENT_NAME,
23
+ ACTIVITY_PARAM_COMPONENT_TYPE
24
+ } from '../src/activities/params.js';
25
+ import {registerActivityControl} from '../src/activities/rules.js';
26
+ import {
27
+ ACTIVITY_ACCESS_DEVICE,
28
+ ACTIVITY_ENRICH_EIDS,
29
+ ACTIVITY_FETCH_BIDS,
30
+ ACTIVITY_REPORT_ANALYTICS,
31
+ ACTIVITY_SYNC_USER
32
+ } from '../src/activities/activities.js';
21
33
 
22
34
  export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement';
23
35
 
24
36
  const TCF2 = {
25
- 'purpose1': { id: 1, name: 'storage' },
26
- 'purpose2': { id: 2, name: 'basicAds' },
27
- 'purpose7': { id: 7, name: 'measurement' }
28
- }
37
+ 'purpose1': {id: 1, name: 'storage'},
38
+ 'purpose2': {id: 2, name: 'basicAds'},
39
+ 'purpose7': {id: 7, name: 'measurement'}
40
+ };
29
41
 
30
42
  /*
31
43
  These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher.
@@ -48,9 +60,9 @@ export let purpose7Rule;
48
60
 
49
61
  export let enforcementRules;
50
62
 
51
- const storageBlocked = [];
52
- const biddersBlocked = [];
53
- const analyticsBlocked = [];
63
+ const storageBlocked = new Set();
64
+ const biddersBlocked = new Set();
65
+ const analyticsBlocked = new Set();
54
66
 
55
67
  let hooksAdded = false;
56
68
  let strictStorageEnforcement = false;
@@ -62,6 +74,9 @@ const GVLID_LOOKUP_PRIORITY = [
62
74
  MODULE_TYPE_RTD
63
75
  ];
64
76
 
77
+ const RULE_NAME = 'TCF2';
78
+ const RULE_HANDLES = [];
79
+
65
80
  /**
66
81
  * Retrieve a module's GVL ID.
67
82
  */
@@ -73,7 +88,7 @@ export function getGvlid(moduleType, moduleName, fallbackFn) {
73
88
  // Return GVL ID from user defined gvlMapping
74
89
  if (gvlMapping && gvlMapping[moduleName]) {
75
90
  return gvlMapping[moduleName];
76
- } else if (moduleType === MODULE_TYPE_CORE) {
91
+ } else if (moduleType === MODULE_TYPE_PREBID) {
77
92
  return VENDORLESS_GVLID;
78
93
  } else {
79
94
  let {gvlid, modules} = GDPR_GVLIDS.get(moduleName);
@@ -83,8 +98,8 @@ export function getGvlid(moduleType, moduleName, fallbackFn) {
83
98
  for (const type of GVLID_LOOKUP_PRIORITY) {
84
99
  if (modules.hasOwnProperty(type)) {
85
100
  gvlid = modules[type];
86
- if (type !== moduleType && !fallbackFn) {
87
- logWarn(`Multiple GVL IDs found for module '${moduleName}'; using the ${type} module's ID (${gvlid}) instead of the ${moduleType}'s ID (${modules[moduleType]})`)
101
+ if (type !== moduleType) {
102
+ logWarn(`Multiple GVL IDs found for module '${moduleName}'; using the ${type} module's ID (${gvlid}) instead of the ${moduleType}'s ID (${modules[moduleType]})`);
88
103
  }
89
104
  break;
90
105
  }
@@ -109,9 +124,9 @@ export function getGvlidFromAnalyticsAdapter(code, config) {
109
124
  try {
110
125
  return gvlid.call(adapter.adapter, config);
111
126
  } catch (e) {
112
- logError(`Error invoking ${code} adapter.gvlid()`, e)
127
+ logError(`Error invoking ${code} adapter.gvlid()`, e);
113
128
  }
114
- })(adapter?.adapter?.gvlid)
129
+ })(adapter?.adapter?.gvlid);
115
130
  }
116
131
 
117
132
  export function shouldEnforce(consentData, purpose, name) {
@@ -120,7 +135,7 @@ export function shouldEnforce(consentData, purpose, name) {
120
135
  // NOTE: this check is not foolproof, as when Prebid first loads, enforcement hooks have not been attached yet
121
136
  // This piece of code would not run at all, and `gdprDataHandler.enabled` would be false, until the first
122
137
  // `setConfig({consentManagement})`
123
- logWarn(`Attempting operation that requires purpose ${purpose} consent while consent data is not available${name ? ` (module: ${name})` : ''}. Assuming no consent was given.`)
138
+ logWarn(`Attempting operation that requires purpose ${purpose} consent while consent data is not available${name ? ` (module: ${name})` : ''}. Assuming no consent was given.`);
124
139
  return true;
125
140
  }
126
141
  return consentData && consentData.gdprApplies;
@@ -142,7 +157,7 @@ export function validateRules(rule, consentData, currentModule, gvlId) {
142
157
  if ((rule.vendorExceptions || []).includes(currentModule)) {
143
158
  return true;
144
159
  }
145
- const vendorConsentRequred = !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule)))
160
+ const vendorConsentRequred = !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule)));
146
161
 
147
162
  // get data from the consent string
148
163
  const purposeConsent = deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`);
@@ -169,170 +184,80 @@ export function validateRules(rule, consentData, currentModule, gvlId) {
169
184
  }
170
185
 
171
186
  /**
172
- * This hook checks whether module has permission to access device or not. Device access include cookie and local storage
187
+ * all activity rules follow the same structure:
188
+ * if GDPR is in scope, check configuration for a particular purpose, and if that enables enforcement,
189
+ * check against consent data for that purpose and vendor
173
190
  *
174
- * @param {Function} fn reference to original function (used by hook logic)
175
- * @param {string} moduleType type of the module
176
- * @param {string=} moduleName name of the module
177
- * @param result
178
- * @param validate
191
+ * @param purposeNo TCF purpose number to check for this activity
192
+ * @param getEnforcementRule getter for gdprEnforcement rule definition to use
193
+ * @param blocked optional set to use for collecting denied vendors
194
+ * @param gvlidFallback optional factory function for a gvlid falllback function
179
195
  */
180
- export function deviceAccessHook(fn, moduleType, moduleName, result, {validate = validateRules} = {}) {
181
- result = Object.assign({}, {
182
- hasEnforcementHook: true
183
- });
184
- if (!hasDeviceAccess()) {
185
- logWarn('Device access is disabled by Publisher');
186
- result.valid = false;
187
- } else if (moduleType === MODULE_TYPE_CORE && !strictStorageEnforcement) {
188
- // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set
189
- result.valid = true;
190
- } else {
196
+ function gdprRule(purposeNo, getEnforcementRule, blocked = null, gvlidFallback = () => null) {
197
+ return function (params) {
191
198
  const consentData = gdprDataHandler.getConsentData();
192
- let gvlid;
193
- if (shouldEnforce(consentData, 1, moduleName)) {
194
- const curBidder = config.getCurrentBidder();
195
- // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder
196
- if (curBidder && (curBidder !== moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) {
197
- gvlid = getGvlid(moduleType, curBidder);
198
- } else {
199
- gvlid = getGvlid(moduleType, moduleName)
200
- }
201
- const curModule = moduleName || curBidder;
202
- let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid,);
203
- if (isAllowed) {
204
- result.valid = true;
205
- } else {
206
- curModule && logWarn(`TCF2 denied device access for ${curModule}`);
207
- result.valid = false;
208
- storageBlocked.push(curModule);
199
+ const modName = params[ACTIVITY_PARAM_COMPONENT_NAME];
200
+ if (shouldEnforce(consentData, purposeNo, modName)) {
201
+ const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params));
202
+ let allow = !!validateRules(getEnforcementRule(), consentData, modName, gvlid);
203
+ if (!allow) {
204
+ blocked && blocked.add(modName);
205
+ return {allow};
209
206
  }
210
- } else {
211
- result.valid = true;
212
207
  }
213
- }
214
- fn.call(this, moduleType, moduleName, result);
208
+ };
215
209
  }
216
210
 
217
- /**
218
- * This hook checks if a bidder has consent for user sync or not
219
- * @param {Function} fn reference to original function (used by hook logic)
220
- * @param {...any} args args
221
- */
222
- export function userSyncHook(fn, ...args) {
223
- const consentData = gdprDataHandler.getConsentData();
224
- const curBidder = config.getCurrentBidder();
225
- if (shouldEnforce(consentData, 1, curBidder)) {
226
- const gvlid = getGvlid(MODULE_TYPE_BIDDER, curBidder);
227
- let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid);
228
- if (isAllowed) {
229
- fn.call(this, ...args);
230
- } else {
231
- logWarn(`User sync not allowed for ${curBidder}`);
232
- storageBlocked.push(curBidder);
233
- }
234
- } else {
235
- fn.call(this, ...args);
236
- }
237
- }
211
+ export const accessDeviceRule = ((rule) => {
212
+ return function (params) {
213
+ // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set
214
+ if (params[ACTIVITY_PARAM_COMPONENT_TYPE] === MODULE_TYPE_PREBID && !strictStorageEnforcement) return;
215
+ return rule(params);
216
+ };
217
+ })(gdprRule(1, () => purpose1Rule, storageBlocked));
218
+
219
+ export const syncUserRule = gdprRule(1, () => purpose1Rule, storageBlocked);
220
+ export const enrichEidsRule = gdprRule(1, () => purpose1Rule, storageBlocked);
238
221
 
239
- /**
240
- * This hook checks if user id module is given consent or not
241
- * @param {Function} fn reference to original function (used by hook logic)
242
- * @param {Submodule[]} submodules Array of user id submodules
243
- * @param {Object} consentData GDPR consent data
244
- */
245
222
  export function userIdHook(fn, submodules, consentData) {
223
+ // TODO: remove this in v8 (https://github.com/prebid/Prebid.js/issues/9766)
246
224
  if (shouldEnforce(consentData, 1, 'User ID')) {
247
- let userIdModules = submodules.map((submodule) => {
248
- const moduleName = submodule.submodule.name;
249
- const gvlid = getGvlid(MODULE_TYPE_UID, moduleName);
250
- let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid);
251
- if (isAllowed) {
252
- return submodule;
253
- } else {
254
- logWarn(`User denied permission to fetch user id for ${moduleName} User id module`);
255
- storageBlocked.push(moduleName);
256
- }
257
- return undefined;
258
- }).filter(module => module)
259
- fn.call(this, userIdModules, { ...consentData, hasValidated: true });
225
+ fn.call(this, submodules, {...consentData, hasValidated: true});
260
226
  } else {
261
227
  fn.call(this, submodules, consentData);
262
228
  }
263
229
  }
264
230
 
265
- /**
266
- * Checks if bidders are allowed in the auction.
267
- * Enforces "purpose 2 (Basic Ads)" of TCF v2.0 spec
268
- * @param {Function} fn - Function reference to the original function.
269
- * @param {Array<adUnits>} adUnits
270
- */
271
- export function makeBidRequestsHook(fn, adUnits, ...args) {
272
- const consentData = gdprDataHandler.getConsentData();
273
- if (shouldEnforce(consentData, 2)) {
274
- adUnits.forEach(adUnit => {
275
- adUnit.bids = adUnit.bids.filter(bid => {
276
- const currBidder = bid.bidder;
277
- const gvlId = getGvlid(MODULE_TYPE_BIDDER, currBidder);
278
- if (includes(biddersBlocked, currBidder)) return false;
279
- const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId);
280
- if (!isAllowed) {
281
- logWarn(`TCF2 blocked auction for ${currBidder}`);
282
- biddersBlocked.push(currBidder);
283
- }
284
- return isAllowed;
285
- });
286
- });
287
- fn.call(this, adUnits, ...args);
288
- } else {
289
- fn.call(this, adUnits, ...args);
290
- }
291
- }
292
-
293
- /**
294
- * Checks if Analytics adapters are allowed to send data to their servers for furhter processing.
295
- * Enforces "purpose 7 (Measurement)" of TCF v2.0 spec
296
- * @param {Function} fn - Function reference to the original function.
297
- * @param {Array<AnalyticsAdapterConfig>} config - Configuration object passed to pbjs.enableAnalytics()
298
- */
299
- export function enableAnalyticsHook(fn, config) {
300
- const consentData = gdprDataHandler.getConsentData();
301
- if (shouldEnforce(consentData, 7, 'Analytics')) {
302
- if (!isArray(config)) {
303
- config = [config]
231
+ export const fetchBidsRule = ((rule) => {
232
+ return function (params) {
233
+ if (params[ACTIVITY_PARAM_COMPONENT_TYPE] !== MODULE_TYPE_BIDDER) {
234
+ // TODO: this special case is for the PBS adapter (componentType is 'prebid')
235
+ // we should check for generic purpose 2 consent & vendor consent based on the PBS vendor's GVL ID;
236
+ // that is, however, a breaking change and skipped for now
237
+ return;
304
238
  }
305
- config = config.filter(conf => {
306
- const analyticsAdapterCode = conf.provider;
307
- const gvlid = getGvlid(MODULE_TYPE_ANALYTICS, analyticsAdapterCode, () => getGvlidFromAnalyticsAdapter(analyticsAdapterCode, conf));
308
- const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid);
309
- if (!isAllowed) {
310
- analyticsBlocked.push(analyticsAdapterCode);
311
- logWarn(`TCF2 blocked analytics adapter ${conf.provider}`);
312
- }
313
- return isAllowed;
314
- });
315
- fn.call(this, config);
316
- } else {
317
- fn.call(this, config);
318
- }
319
- }
239
+ return rule(params);
240
+ };
241
+ })(gdprRule(2, () => purpose2Rule, biddersBlocked));
242
+
243
+ export const reportAnalyticsRule = gdprRule(7, () => purpose7Rule, analyticsBlocked, (params) => getGvlidFromAnalyticsAdapter(params[ACTIVITY_PARAM_COMPONENT_NAME], params[ACTIVITY_PARAM_ANL_CONFIG]));
320
244
 
321
245
  /**
322
246
  * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event.
323
247
  */
324
248
  function emitTCF2FinalResults() {
325
249
  // remove null and duplicate values
326
- const formatArray = function (arr) {
327
- return arr.filter((i, k) => i !== null && arr.indexOf(i) === k);
328
- }
250
+ const formatSet = function (st) {
251
+ return Array.from(st.keys()).filter(el => el != null);
252
+ };
329
253
  const tcf2FinalResults = {
330
- storageBlocked: formatArray(storageBlocked),
331
- biddersBlocked: formatArray(biddersBlocked),
332
- analyticsBlocked: formatArray(analyticsBlocked)
254
+ storageBlocked: formatSet(storageBlocked),
255
+ biddersBlocked: formatSet(biddersBlocked),
256
+ analyticsBlocked: formatSet(analyticsBlocked)
333
257
  };
334
258
 
335
259
  events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults);
260
+ [storageBlocked, biddersBlocked, analyticsBlocked].forEach(el => el.clear());
336
261
  }
337
262
 
338
263
  events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults);
@@ -340,9 +265,15 @@ events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults);
340
265
  /*
341
266
  Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find().
342
267
  */
343
- const hasPurpose1 = (rule) => { return rule.purpose === TCF2.purpose1.name }
344
- const hasPurpose2 = (rule) => { return rule.purpose === TCF2.purpose2.name }
345
- const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name }
268
+ const hasPurpose1 = (rule) => {
269
+ return rule.purpose === TCF2.purpose1.name;
270
+ };
271
+ const hasPurpose2 = (rule) => {
272
+ return rule.purpose === TCF2.purpose2.name;
273
+ };
274
+ const hasPurpose7 = (rule) => {
275
+ return rule.purpose === TCF2.purpose7.name;
276
+ };
346
277
 
347
278
  /**
348
279
  * A configuration function that initializes some module variables, as well as adds hooks
@@ -373,27 +304,25 @@ export function setEnforcementConfig(config) {
373
304
  if (!hooksAdded) {
374
305
  if (purpose1Rule) {
375
306
  hooksAdded = true;
376
- validateStorageEnforcement.before(deviceAccessHook, 49);
377
- registerSyncInner.before(userSyncHook, 48);
378
- // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build
307
+ RULE_HANDLES.push(registerActivityControl(ACTIVITY_ACCESS_DEVICE, RULE_NAME, accessDeviceRule));
308
+ RULE_HANDLES.push(registerActivityControl(ACTIVITY_SYNC_USER, RULE_NAME, syncUserRule));
309
+ RULE_HANDLES.push(registerActivityControl(ACTIVITY_ENRICH_EIDS, RULE_NAME, enrichEidsRule));
310
+ // TODO: remove this hook in v8 (https://github.com/prebid/Prebid.js/issues/9766)
379
311
  getHook('validateGdprEnforcement').before(userIdHook, 47);
380
312
  }
381
313
  if (purpose2Rule) {
382
- getHook('makeBidRequests').before(makeBidRequestsHook);
314
+ RULE_HANDLES.push(registerActivityControl(ACTIVITY_FETCH_BIDS, RULE_NAME, fetchBidsRule));
383
315
  }
384
316
  if (purpose7Rule) {
385
- getHook('enableAnalyticsCb').before(enableAnalyticsHook);
317
+ RULE_HANDLES.push(registerActivityControl(ACTIVITY_REPORT_ANALYTICS, RULE_NAME, reportAnalyticsRule));
386
318
  }
387
319
  }
388
320
  }
389
321
 
390
322
  export function uninstall() {
323
+ while (RULE_HANDLES.length) RULE_HANDLES.pop()();
391
324
  [
392
- validateStorageEnforcement.getHooks({hook: deviceAccessHook}),
393
- registerSyncInner.getHooks({hook: userSyncHook}),
394
325
  getHook('validateGdprEnforcement').getHooks({hook: userIdHook}),
395
- getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}),
396
- getHook('enableAnalyticsCb').getHooks({hook: enableAnalyticsHook}),
397
326
  ].forEach(hook => hook.remove());
398
327
  hooksAdded = false;
399
328
  }