prebid.js 6.5.0 → 6.6.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 (56) hide show
  1. package/modules/.submodules.json +2 -1
  2. package/modules/adkernelBidAdapter.js +2 -1
  3. package/modules/admanBidAdapter.js +10 -4
  4. package/modules/adomikAnalyticsAdapter.js +23 -11
  5. package/modules/adqueryIdSystem.js +103 -0
  6. package/modules/adqueryIdSystem.md +35 -0
  7. package/modules/bliinkBidAdapter.js +2 -1
  8. package/modules/compassBidAdapter.js +9 -2
  9. package/modules/criteoBidAdapter.js +1 -1
  10. package/modules/criteoIdSystem.js +29 -7
  11. package/modules/glimpseBidAdapter.js +66 -44
  12. package/modules/idImportLibrary.js +45 -8
  13. package/modules/idImportLibrary.md +4 -0
  14. package/modules/improvedigitalBidAdapter.js +24 -2
  15. package/modules/nextMillenniumBidAdapter.js +3 -1
  16. package/modules/oguryBidAdapter.js +9 -2
  17. package/modules/onetagBidAdapter.js +4 -2
  18. package/modules/proxistoreBidAdapter.js +0 -2
  19. package/modules/pubmaticAnalyticsAdapter.js +16 -0
  20. package/modules/richaudienceBidAdapter.js +3 -2
  21. package/modules/riseBidAdapter.js +1 -1
  22. package/modules/rtbhouseBidAdapter.js +14 -4
  23. package/modules/rtdModule/index.js +6 -5
  24. package/modules/showheroes-bsBidAdapter.js +13 -2
  25. package/modules/tappxBidAdapter.js +8 -5
  26. package/modules/teadsBidAdapter.js +1 -2
  27. package/modules/userId/eids.js +7 -1
  28. package/modules/userId/userId.md +8 -0
  29. package/modules/welectBidAdapter.js +106 -0
  30. package/package.json +1 -1
  31. package/src/hook.js +5 -1
  32. package/src/prebid.js +18 -4
  33. package/test/spec/modules/admanBidAdapter_spec.js +2 -2
  34. package/test/spec/modules/adomikAnalyticsAdapter_spec.js +3 -1
  35. package/test/spec/modules/adqueryIdSystem_spec.js +74 -0
  36. package/test/spec/modules/bliinkBidAdapter_spec.js +2 -0
  37. package/test/spec/modules/compassBidAdapter_spec.js +1 -0
  38. package/test/spec/modules/criteoIdSystem_spec.js +6 -3
  39. package/test/spec/modules/eids_spec.js +15 -0
  40. package/test/spec/modules/glimpseBidAdapter_spec.js +0 -18
  41. package/test/spec/modules/idImportLibrary_spec.js +197 -10
  42. package/test/spec/modules/improvedigitalBidAdapter_spec.js +42 -0
  43. package/test/spec/modules/loglyliftBidAdapter_spec.js +1 -1
  44. package/test/spec/modules/nextMillenniumBidAdapter_spec.js +1 -1
  45. package/test/spec/modules/oguryBidAdapter_spec.js +10 -2
  46. package/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +13 -1
  47. package/test/spec/modules/realTimeDataModule_spec.js +16 -3
  48. package/test/spec/modules/richaudienceBidAdapter_spec.js +40 -0
  49. package/test/spec/modules/riseBidAdapter_spec.js +1 -1
  50. package/test/spec/modules/rtbhouseBidAdapter_spec.js +20 -0
  51. package/test/spec/modules/showheroes-bsBidAdapter_spec.js +2 -0
  52. package/test/spec/modules/tappxBidAdapter_spec.js +0 -19
  53. package/test/spec/modules/teadsBidAdapter_spec.js +14 -59
  54. package/test/spec/modules/userId_spec.js +68 -19
  55. package/test/spec/modules/welectBidAdapter_spec.js +211 -0
  56. package/test/spec/unit/pbjs_api_spec.js +3 -1
@@ -35,7 +35,8 @@
35
35
  "uid2IdSystem",
36
36
  "unifiedIdSystem",
37
37
  "verizonMediaIdSystem",
38
- "zeotapIdPlusIdSystem"
38
+ "zeotapIdPlusIdSystem",
39
+ "adqueryIdSystem"
39
40
  ],
40
41
  "adpod": [
41
42
  "freeWheelAdserverVideo",
@@ -77,7 +77,8 @@ export const spec = {
77
77
  {code: 'unibots'},
78
78
  {code: 'catapultx'},
79
79
  {code: 'ergadx'},
80
- {code: 'turktelekom'}
80
+ {code: 'turktelekom'},
81
+ {code: 'felixads'}
81
82
  ],
82
83
  supportedMediaTypes: [BANNER, VIDEO, NATIVE],
83
84
 
@@ -1,10 +1,11 @@
1
1
  import {registerBidder} from '../src/adapters/bidderFactory.js';
2
2
  import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
3
3
  import { isFn, deepAccess, logMessage } from '../src/utils.js';
4
+ import {config} from '../src/config.js';
4
5
 
5
6
  const BIDDER_CODE = 'adman';
6
7
  const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi';
7
- const URL_SYNC = 'https://pub.admanmedia.com/?c=o&m=sync';
8
+ const URL_SYNC = 'https://pub.admanmedia.com';
8
9
 
9
10
  function isBidResponseValid(bid) {
10
11
  if (!bid.requestId || !bid.cpm || !bid.creativeId ||
@@ -152,19 +153,24 @@ export const spec = {
152
153
  },
153
154
 
154
155
  getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
155
- let syncUrl = URL_SYNC
156
+ let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image';
157
+ let syncUrl = URL_SYNC + `/${syncType}?pbjs=1`;
156
158
  if (gdprConsent && gdprConsent.consentString) {
157
159
  if (typeof gdprConsent.gdprApplies === 'boolean') {
158
160
  syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
159
161
  } else {
160
- syncUrl += `&gdpr==0&gdpr_consent=${gdprConsent.consentString}`;
162
+ syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
161
163
  }
162
164
  }
163
165
  if (uspConsent && uspConsent.consentString) {
164
166
  syncUrl += `&ccpa_consent=${uspConsent.consentString}`;
165
167
  }
168
+
169
+ const coppa = config.getConfig('coppa') ? 1 : 0;
170
+ syncUrl += `&coppa=${coppa}`;
171
+
166
172
  return [{
167
- type: 'image',
173
+ type: syncType,
168
174
  url: syncUrl
169
175
  }];
170
176
  }
@@ -14,6 +14,8 @@ const bidWon = CONSTANTS.EVENTS.BID_WON;
14
14
  const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT;
15
15
  const ua = navigator.userAgent;
16
16
 
17
+ var _sampled = true;
18
+
17
19
  let adomikAdapter = Object.assign(adapter({}),
18
20
  {
19
21
  // Track every event needed
@@ -81,6 +83,7 @@ adomikAdapter.sendTypedEvent = function() {
81
83
  uid: adomikAdapter.currentContext.uid,
82
84
  ahbaid: adomikAdapter.currentContext.id,
83
85
  hostname: window.location.hostname,
86
+ sampling: adomikAdapter.currentContext.sampling,
84
87
  eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) {
85
88
  let sizes = [];
86
89
  const eventKeys = ['request', 'response', 'winner'];
@@ -203,19 +206,28 @@ adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics;
203
206
 
204
207
  adomikAdapter.enableAnalytics = function (config) {
205
208
  adomikAdapter.currentContext = {};
206
-
207
209
  const initOptions = config.options;
208
- if (initOptions) {
209
- adomikAdapter.currentContext = {
210
- uid: initOptions.id,
211
- url: initOptions.url,
212
- testId: initOptions.testId,
213
- testValue: initOptions.testValue,
214
- id: '',
215
- timeouted: false,
210
+
211
+ _sampled = typeof config === 'undefined' ||
212
+ typeof config.sampling === 'undefined' ||
213
+ Math.random() < parseFloat(config.sampling);
214
+
215
+ if (_sampled) {
216
+ if (initOptions) {
217
+ adomikAdapter.currentContext = {
218
+ uid: initOptions.id,
219
+ url: initOptions.url,
220
+ testId: initOptions.testId,
221
+ testValue: initOptions.testValue,
222
+ id: '',
223
+ timeouted: false,
224
+ sampling: config.sampling
225
+ }
226
+ logInfo('Adomik Analytics enabled with config', initOptions);
227
+ adomikAdapter.adapterEnableAnalytics(config);
216
228
  }
217
- logInfo('Adomik Analytics enabled with config', initOptions);
218
- adomikAdapter.adapterEnableAnalytics(config);
229
+ } else {
230
+ logInfo('Adomik Analytics ignored for sampling', config.sampling);
219
231
  }
220
232
  };
221
233
 
@@ -0,0 +1,103 @@
1
+ /**
2
+ * This module adds Adquery QID to the User ID module
3
+ * The {@link module:modules/userId} module is required
4
+ * @module modules/adqueryIdSystem
5
+ * @requires module:modules/userId
6
+ */
7
+
8
+ import {ajax} from '../src/ajax.js';
9
+ import {getStorageManager} from '../src/storageManager.js';
10
+ import {submodule} from '../src/hook.js';
11
+ import * as utils from '../src/utils.js';
12
+
13
+ const MODULE_NAME = 'qid';
14
+ const AU_GVLID = 902;
15
+
16
+ export const storage = getStorageManager(AU_GVLID, 'qid');
17
+
18
+ /**
19
+ * Param or default.
20
+ * @param {String} param
21
+ * @param {String} defaultVal
22
+ */
23
+ function paramOrDefault(param, defaultVal, arg) {
24
+ if (utils.isFn(param)) {
25
+ return param(arg);
26
+ } else if (utils.isStr(param)) {
27
+ return param;
28
+ }
29
+ return defaultVal;
30
+ }
31
+
32
+ /** @type {Submodule} */
33
+ export const adqueryIdSubmodule = {
34
+ /**
35
+ * used to link submodule with config
36
+ * @type {string}
37
+ */
38
+ name: MODULE_NAME,
39
+
40
+ /**
41
+ * IAB TCF Vendor ID
42
+ * @type {string}
43
+ */
44
+ gvlid: AU_GVLID,
45
+
46
+ /**
47
+ * decode the stored id value for passing to bid requests
48
+ * @function
49
+ * @param {{value:string}} value
50
+ * @returns {{qid:Object}}
51
+ */
52
+ decode(value) {
53
+ let qid = storage.getDataFromLocalStorage('qid');
54
+ if (utils.isStr(qid)) {
55
+ return {qid: qid};
56
+ }
57
+ return (value && typeof value['qid'] === 'string') ? { 'qid': value['qid'] } : undefined;
58
+ },
59
+ /**
60
+ * performs action to obtain id and return a value in the callback's response argument
61
+ * @function
62
+ * @param {SubmoduleConfig} [config]
63
+ * @returns {IdResponse|undefined}
64
+ */
65
+ getId(config) {
66
+ if (!utils.isPlainObject(config.params)) {
67
+ config.params = {};
68
+ }
69
+ const url = paramOrDefault(config.params.url,
70
+ `https://bidder.adquery.io/prebid/qid`,
71
+ config.params.urlArg);
72
+
73
+ const resp = function (callback) {
74
+ let qid = storage.getDataFromLocalStorage('qid');
75
+ if (utils.isStr(qid)) {
76
+ const responseObj = {qid: qid};
77
+ callback(responseObj);
78
+ } else {
79
+ const callbacks = {
80
+ success: response => {
81
+ let responseObj;
82
+ if (response) {
83
+ try {
84
+ responseObj = JSON.parse(response);
85
+ } catch (error) {
86
+ utils.logError(error);
87
+ }
88
+ }
89
+ callback(responseObj);
90
+ },
91
+ error: error => {
92
+ utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
93
+ callback();
94
+ }
95
+ };
96
+ ajax(url, callbacks, undefined, {method: 'GET'});
97
+ }
98
+ };
99
+ return {callback: resp};
100
+ }
101
+ };
102
+
103
+ submodule('userId', adqueryIdSubmodule);
@@ -0,0 +1,35 @@
1
+ # Adquery QID
2
+
3
+ Adquery QID Module. For assistance setting up your module please contact us at [prebid@adquery.io](prebid@adquery.io).
4
+
5
+ ### Prebid Params
6
+
7
+ Individual params may be set for the Adquery ID Submodule. At least one identifier must be set in the params.
8
+
9
+ ```
10
+ pbjs.setConfig({
11
+ usersync: {
12
+ userIds: [{
13
+ name: 'qid',
14
+ storage: {
15
+ name: 'qid',
16
+ type: 'html5'
17
+ }
18
+ }]
19
+ }
20
+ });
21
+ ```
22
+ ## Parameter Descriptions for the `usersync` Configuration Section
23
+ The below parameters apply only to the Adquery User ID Module integration.
24
+
25
+ | Param under usersync.userIds[] | Scope | Type | Description | Example |
26
+ | --- | --- | --- | --- | --- |
27
+ | name | Required | String | ID value for the Adquery ID module - `"qid"` | `"qid"` |
28
+ | storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. | |
29
+ | storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` |
30
+ | storage.name | Required | String | The name of the html5 local storage where the user ID will be stored. | `"qid"` |
31
+ | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` |
32
+ | value | Optional | Object | Used only if the page has a separate mechanism for storing the Adquery ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"qid": "2abf9f001fcd81241b67"}` |
33
+ | params | Optional | Object | Used to store params for the id system |
34
+ | params.url | Optional | String | Set an alternate GET url for qid with this parameter |
35
+ | params.urlArg | Optional | Object | Optional url parameter for params.url |
@@ -1,7 +1,6 @@
1
1
  // eslint-disable-next-line prebid/validate-imports
2
2
  // eslint-disable-next-line prebid/validate-imports
3
3
  import {registerBidder} from '../src/adapters/bidderFactory.js'
4
-
5
4
  export const BIDDER_CODE = 'bliink'
6
5
  export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery'
7
6
  export const BLIINK_ENDPOINT_ENGINE_VAST = 'https://engine.bliink.io/vast'
@@ -174,6 +173,8 @@ export const buildRequests = (_, bidderRequest) => {
174
173
  pageUrl: bidderRequest.refererInfo.referer,
175
174
  pageDescription: getMetaValue(META_DESCRIPTION),
176
175
  keywords: getKeywords().join(','),
176
+ gdpr: false,
177
+ gdpr_consent: '',
177
178
  pageTitle: document.title,
178
179
  }
179
180
 
@@ -28,16 +28,23 @@ function isBidResponseValid(bid) {
28
28
  function getPlacementReqData(bid) {
29
29
  const { params, bidId, mediaTypes } = bid;
30
30
  const schain = bid.schain || {};
31
- const { placementId } = params;
31
+ const { placementId, endpointId } = params;
32
32
  const bidfloor = getBidFloor(bid);
33
33
 
34
34
  const placement = {
35
- placementId,
36
35
  bidId,
37
36
  schain,
38
37
  bidfloor
39
38
  };
40
39
 
40
+ if (placementId) {
41
+ placement.placementId = placementId;
42
+ placement.type = 'publisher';
43
+ } else if (endpointId) {
44
+ placement.endpointId = endpointId;
45
+ placement.type = 'network';
46
+ }
47
+
41
48
  if (mediaTypes && mediaTypes[BANNER]) {
42
49
  placement.adFormat = BANNER;
43
50
  placement.sizes = mediaTypes[BANNER].sizes;
@@ -24,7 +24,7 @@ const LOG_PREFIX = 'Criteo: ';
24
24
  Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js
25
25
  */
26
26
  const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%';
27
- export const FAST_BID_VERSION_CURRENT = 116;
27
+ export const FAST_BID_VERSION_CURRENT = 117;
28
28
  const FAST_BID_VERSION_LATEST = 'latest';
29
29
  const FAST_BID_VERSION_NONE = 'none';
30
30
  const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js';
@@ -33,15 +33,37 @@ function getFromAllStorages(key) {
33
33
  return storage.getCookie(key) || storage.getDataFromLocalStorage(key);
34
34
  }
35
35
 
36
- function saveOnAllStorages(key, value) {
36
+ function saveOnAllStorages(key, value, hostname) {
37
37
  if (key && value) {
38
- storage.setCookie(key, value, expirationString);
39
38
  storage.setDataInLocalStorage(key, value);
39
+ setCookieOnAllDomains(key, value, expirationString, hostname, true);
40
40
  }
41
41
  }
42
42
 
43
- function deleteFromAllStorages(key) {
44
- storage.setCookie(key, '', pastDateString);
43
+ function setCookieOnAllDomains(key, value, expiration, hostname, stopOnSuccess) {
44
+ const subDomains = hostname.split('.');
45
+ for (let i = 0; i < subDomains.length; ++i) {
46
+ // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1)
47
+ const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.');
48
+
49
+ try {
50
+ storage.setCookie(key, value, expiration, null, '.' + domain);
51
+
52
+ if (stopOnSuccess) {
53
+ // Try to read the cookie to check if we wrote it
54
+ const ck = storage.getCookie(key);
55
+ if (ck && ck === value) {
56
+ break;
57
+ }
58
+ }
59
+ } catch (error) {
60
+
61
+ }
62
+ }
63
+ }
64
+
65
+ function deleteFromAllStorages(key, hostname) {
66
+ setCookieOnAllDomains(key, '', pastDateString, hostname, true);
45
67
  storage.removeDataFromLocalStorage(key);
46
68
  }
47
69
 
@@ -89,15 +111,15 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) {
89
111
  const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl;
90
112
  urlsToCall.forEach(url => triggerPixel(url));
91
113
  } else if (jsonResponse.bundle) {
92
- saveOnAllStorages(bundleStorageKey, jsonResponse.bundle);
114
+ saveOnAllStorages(bundleStorageKey, jsonResponse.bundle, domain);
93
115
  }
94
116
 
95
117
  if (jsonResponse.bidId) {
96
- saveOnAllStorages(bididStorageKey, jsonResponse.bidId);
118
+ saveOnAllStorages(bididStorageKey, jsonResponse.bidId, domain);
97
119
  const criteoId = { criteoId: jsonResponse.bidId };
98
120
  callback(criteoId);
99
121
  } else {
100
- deleteFromAllStorages(bididStorageKey);
122
+ deleteFromAllStorages(bididStorageKey, domain);
101
123
  callback();
102
124
  }
103
125
  },
@@ -40,28 +40,21 @@ export const spec = {
40
40
  * @returns {ServerRequest}
41
41
  */
42
42
  buildRequests: (validBidRequests, bidderRequest) => {
43
- const demo = config.getConfig('glimpse.demo') || false
44
- const account = config.getConfig('glimpse.account') || -1
45
- const demand = config.getConfig('glimpse.demand') || 'glimpse'
46
- const keywords = config.getConfig('glimpse.keywords') || {}
47
-
48
43
  const auth = getVaultJwt()
49
44
  const referer = getReferer(bidderRequest)
50
45
  const gdprConsent = getGdprConsentChoice(bidderRequest)
51
- const bids = validBidRequests.map((bidRequest) => {
52
- return processBidRequest(bidRequest, keywords)
53
- })
46
+ const bidRequests = validBidRequests.map(processBidRequest)
47
+ const firstPartyData = getFirstPartyData()
54
48
 
55
49
  const data = {
56
50
  auth,
57
51
  data: {
58
- bidderCode: spec.code,
59
- demo,
60
- account,
61
- demand,
62
52
  referer,
63
53
  gdprConsent,
64
- bids,
54
+ bidRequests,
55
+ site: firstPartyData.site,
56
+ user: firstPartyData.user,
57
+ bidderCode: spec.code,
65
58
  }
66
59
  }
67
60
 
@@ -91,35 +84,12 @@ export const spec = {
91
84
  },
92
85
  }
93
86
 
94
- function processBidRequest(bidRequest, globalKeywords) {
95
- const sizes = normalizeSizes(bidRequest.sizes)
96
- const bidKeywords = bidRequest.params.keywords || {}
97
- const keywords = {
98
- ...globalKeywords,
99
- ...bidKeywords,
100
- }
101
-
102
- return {
103
- unitCode: bidRequest.adUnitCode,
104
- bidId: bidRequest.bidId,
105
- placementId: bidRequest.params.placementId,
106
- keywords,
107
- sizes,
108
- }
87
+ function setVaultJwt(auth) {
88
+ storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth)
109
89
  }
110
90
 
111
- function normalizeSizes(sizes) {
112
- const isSingleSize =
113
- isArray(sizes) &&
114
- sizes.length === 2 &&
115
- !isArray(sizes[0]) &&
116
- !isArray(sizes[1])
117
-
118
- if (isSingleSize) {
119
- return [sizes]
120
- }
121
-
122
- return sizes
91
+ function getVaultJwt() {
92
+ return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || ''
123
93
  }
124
94
 
125
95
  function getReferer(bidderRequest) {
@@ -158,12 +128,64 @@ function getGdprConsentChoice(bidderRequest) {
158
128
  }
159
129
  }
160
130
 
161
- function setVaultJwt(auth) {
162
- storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth)
131
+ function processBidRequest(bidRequest) {
132
+ const demand = bidRequest.params.demand || 'glimpse'
133
+ const sizes = normalizeSizes(bidRequest.sizes)
134
+ const keywords = bidRequest.params.keywords || {}
135
+
136
+ return {
137
+ demand,
138
+ sizes,
139
+ keywords,
140
+ bidId: bidRequest.bidId,
141
+ placementId: bidRequest.params.placementId,
142
+ unitCode: bidRequest.adUnitCode,
143
+ }
163
144
  }
164
145
 
165
- function getVaultJwt() {
166
- return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || ''
146
+ function normalizeSizes(sizes) {
147
+ const isSingleSize =
148
+ isArray(sizes) &&
149
+ sizes.length === 2 &&
150
+ !isArray(sizes[0]) &&
151
+ !isArray(sizes[1])
152
+
153
+ if (isSingleSize) {
154
+ return [sizes]
155
+ }
156
+
157
+ return sizes
158
+ }
159
+
160
+ function getFirstPartyData() {
161
+ const siteKeywords = parseGlobalKeywords('site')
162
+ const userKeywords = parseGlobalKeywords('user')
163
+
164
+ const siteAttributes = getConfig('ortb2.site.ext.data', {})
165
+ const userAttributes = getConfig('ortb2.user.ext.data', {})
166
+
167
+ return {
168
+ site: {
169
+ keywords: siteKeywords,
170
+ attributes: siteAttributes,
171
+ },
172
+ user: {
173
+ keywords: userKeywords,
174
+ attributes: userAttributes,
175
+ },
176
+ }
177
+ }
178
+
179
+ function parseGlobalKeywords(scope) {
180
+ const keywords = getConfig(`ortb2.${scope}.keywords`, '')
181
+
182
+ return keywords
183
+ .split(', ')
184
+ .filter((keyword) => keyword !== '')
185
+ }
186
+
187
+ function getConfig(path, defaultValue) {
188
+ return config.getConfig(path) || defaultValue
167
189
  }
168
190
 
169
191
  function isValidBidResponse(bidResponse) {
@@ -9,6 +9,7 @@ let conf;
9
9
  const LOG_PRE_FIX = 'ID-Library: ';
10
10
  const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250;
11
11
  const CONF_DEFAULT_FULL_BODY_SCAN = false;
12
+ const CONF_DEFAULT_INPUT_SCAN = false;
12
13
  const OBSERVER_CONFIG = {
13
14
  subtree: true,
14
15
  attributes: true,
@@ -78,7 +79,13 @@ function targetAction(mutations, observer) {
78
79
  }
79
80
  }
80
81
 
81
- function addInputElementsElementListner(conf) {
82
+ function addInputElementsElementListner() {
83
+ if (doesInputElementsHaveEmail()) {
84
+ _logInfo('Email found in input elements ' + email);
85
+ _logInfo('Post data on email found in target without');
86
+ postData();
87
+ return;
88
+ }
82
89
  _logInfo('Adding input element listeners');
83
90
  const inputs = document.querySelectorAll('input[type=text], input[type=email]');
84
91
 
@@ -89,6 +96,19 @@ function addInputElementsElementListner(conf) {
89
96
  }
90
97
  }
91
98
 
99
+ function addFormInputElementsElementListner(id) {
100
+ _logInfo('Adding input element listeners');
101
+ if (doesFormInputElementsHaveEmail(id)) {
102
+ _logInfo('Email found in input elements ' + email);
103
+ postData();
104
+ return;
105
+ }
106
+ _logInfo('Adding input element listeners');
107
+ const input = document.getElementById(id);
108
+ input.addEventListener('change', event => processInputChange(event));
109
+ input.addEventListener('blur', event => processInputChange(event));
110
+ }
111
+
92
112
  function removeInputElementsElementListner() {
93
113
  _logInfo('Removing input element listeners');
94
114
  const inputs = document.querySelectorAll('input[type=text], input[type=email]');
@@ -149,12 +169,6 @@ function handleTargetElement() {
149
169
  }
150
170
 
151
171
  function handleBodyElements() {
152
- if (doesInputElementsHaveEmail()) {
153
- _logInfo('Email found in input elements ' + email);
154
- _logInfo('Post data on email found in target without');
155
- postData();
156
- return;
157
- }
158
172
  email = getEmail(document.body.innerHTML);
159
173
  if (email !== null) {
160
174
  _logInfo('Email found in body ' + email);
@@ -162,7 +176,7 @@ function handleBodyElements() {
162
176
  postData();
163
177
  return;
164
178
  }
165
- addInputElementsElementListner();
179
+
166
180
  if (conf.fullscan === true) {
167
181
  const bodyObserver = new MutationObserver(debounce(bodyAction, conf.debounce, false));
168
182
  bodyObserver.observe(document.body, OBSERVER_CONFIG);
@@ -182,6 +196,17 @@ function doesInputElementsHaveEmail() {
182
196
  return false;
183
197
  }
184
198
 
199
+ function doesFormInputElementsHaveEmail(formElementId) {
200
+ const input = document.getElementById(formElementId);
201
+ if (input) {
202
+ email = getEmail(input.value);
203
+ if (email !== null) {
204
+ return true;
205
+ }
206
+ }
207
+ return false;
208
+ }
209
+
185
210
  function syncCallback() {
186
211
  return {
187
212
  success: function () {
@@ -213,6 +238,10 @@ function associateIds() {
213
238
  if (window.MutationObserver || window.WebKitMutationObserver) {
214
239
  if (conf.target) {
215
240
  handleTargetElement();
241
+ } else if (conf.formElementId) {
242
+ addFormInputElementsElementListner(conf.formElementId);
243
+ } else if (conf.inputscan) {
244
+ addInputElementsElementListner();
216
245
  } else {
217
246
  handleBodyElements();
218
247
  }
@@ -236,6 +265,14 @@ export function setConfig(config) {
236
265
  config.fullscan = CONF_DEFAULT_FULL_BODY_SCAN;
237
266
  _logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN);
238
267
  }
268
+ if (typeof config.inputscan !== 'boolean') {
269
+ config.inputscan = CONF_DEFAULT_INPUT_SCAN;
270
+ _logInfo('Set default input scan ' + CONF_DEFAULT_INPUT_SCAN);
271
+ }
272
+
273
+ if (typeof config.formElementId == 'string') {
274
+ _logInfo('Looking for formElementId ' + config.formElementId);
275
+ }
239
276
  conf = config;
240
277
  associateIds();
241
278
  }
@@ -8,6 +8,8 @@
8
8
  | `url` | Yes | String | N/A | URL endpoint used to post the hashed email and user IDs. |
9
9
  | `debounce` | No | Number | 250 | Time in milliseconds before the email and IDs are fetched. |
10
10
  | `fullscan` | No | Boolean | false | Enable/disable a full page body scan to get email. |
11
+ | `formElementId` | No | String | N/A | ID attribute of the input (type=text/email) from which the email can be read. |
12
+ | `inputscan` | No | Boolean | N/A | Enable/disable a input element (type=text/email) scan to get email. |
11
13
 
12
14
  ## Example
13
15
 
@@ -18,5 +20,7 @@ pbjs.setConfig({
18
20
  url: 'https://example.com',
19
21
  debounce: 250,
20
22
  fullscan: false,
23
+ inputscan: false,
24
+ formElementId: "userid"
21
25
  },
22
26
  });