prebid.js 6.26.0 → 6.27.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 (188) hide show
  1. package/dist/33acrossBidAdapter.js +1 -1
  2. package/dist/adWMGAnalyticsAdapter.js +1 -1
  3. package/dist/adagioAnalyticsAdapter.js +1 -1
  4. package/dist/adagioBidAdapter.js +1 -1
  5. package/dist/adbookpspBidAdapter.js +1 -1
  6. package/dist/adgenerationBidAdapter.js +1 -1
  7. package/dist/adkernelAdnAnalyticsAdapter.js +1 -1
  8. package/dist/adlooxAdServerVideo.js +1 -1
  9. package/dist/adlooxAnalyticsAdapter.js +1 -1
  10. package/dist/adlooxRtdProvider.js +1 -1
  11. package/dist/adomikAnalyticsAdapter.js +1 -1
  12. package/dist/adrelevantisBidAdapter.js +1 -1
  13. package/dist/adxcgAnalyticsAdapter.js +1 -1
  14. package/dist/adxcgBidAdapter.js +1 -1
  15. package/dist/adxpremiumAnalyticsAdapter.js +1 -1
  16. package/dist/ajaBidAdapter.js +1 -1
  17. package/dist/akamaiDapRtdProvider.js +1 -1
  18. package/dist/amxBidAdapter.js +1 -1
  19. package/dist/amxIdSystem.js +1 -1
  20. package/dist/appierAnalyticsAdapter.js +1 -1
  21. package/dist/appnexusAnalyticsAdapter.js +1 -1
  22. package/dist/appnexusBidAdapter.js +1 -1
  23. package/dist/asoBidAdapter.js +1 -1
  24. package/dist/atsAnalyticsAdapter.js +1 -1
  25. package/dist/axonixBidAdapter.js +1 -1
  26. package/dist/bidglassBidAdapter.js +1 -1
  27. package/dist/bidwatchAnalyticsAdapter.js +1 -1
  28. package/dist/big-richmediaBidAdapter.js +1 -1
  29. package/dist/bridgewellBidAdapter.js +1 -1
  30. package/dist/brightMountainMediaBidAdapter.js +1 -1
  31. package/dist/byDataAnalyticsAdapter.js +1 -1
  32. package/dist/colossussspBidAdapter.js +1 -1
  33. package/dist/concertAnalyticsAdapter.js +1 -1
  34. package/dist/concertBidAdapter.js +1 -1
  35. package/dist/connectadBidAdapter.js +1 -1
  36. package/dist/consumableBidAdapter.js +1 -1
  37. package/dist/conversantBidAdapter.js +1 -1
  38. package/dist/craftBidAdapter.js +1 -1
  39. package/dist/criteoBidAdapter.js +1 -1
  40. package/dist/datablocksAnalyticsAdapter.js +1 -1
  41. package/dist/dspxBidAdapter.js +1 -1
  42. package/dist/eplanningAnalyticsAdapter.js +1 -1
  43. package/dist/eplanningBidAdapter.js +1 -1
  44. package/dist/fintezaAnalyticsAdapter.js +1 -1
  45. package/dist/ftrackIdSystem.js +1 -1
  46. package/dist/glimpseBidAdapter.js +1 -1
  47. package/dist/gmosspBidAdapter.js +1 -1
  48. package/dist/goldbachBidAdapter.js +1 -1
  49. package/dist/gridBidAdapter.js +1 -1
  50. package/dist/gridNMBidAdapter.js +1 -1
  51. package/dist/gumgumBidAdapter.js +1 -1
  52. package/dist/h12mediaBidAdapter.js +1 -1
  53. package/dist/hadronAnalyticsAdapter.js +1 -1
  54. package/dist/id5AnalyticsAdapter.js +1 -1
  55. package/dist/id5IdSystem.js +1 -1
  56. package/dist/improvedigitalBidAdapter.js +1 -1
  57. package/dist/inmarBidAdapter.js +1 -1
  58. package/dist/insticatorBidAdapter.js +1 -1
  59. package/dist/invisiblyAnalyticsAdapter.js +1 -1
  60. package/dist/ixBidAdapter.js +1 -1
  61. package/dist/justpremiumBidAdapter.js +1 -1
  62. package/dist/kargoAnalyticsAdapter.js +1 -1
  63. package/dist/koblerBidAdapter.js +1 -1
  64. package/dist/konduitAnalyticsAdapter.js +1 -1
  65. package/dist/livewrappedAnalyticsAdapter.js +1 -1
  66. package/dist/liveyieldAnalyticsAdapter.js +1 -1
  67. package/dist/logicadBidAdapter.js +1 -1
  68. package/dist/loglyliftBidAdapter.js +1 -1
  69. package/dist/malltvAnalyticsAdapter.js +1 -1
  70. package/dist/marsmediaAnalyticsAdapter.js +1 -1
  71. package/dist/marsmediaBidAdapter.js +1 -1
  72. package/dist/mediafuseBidAdapter.js +1 -1
  73. package/dist/medianetAnalyticsAdapter.js +1 -1
  74. package/dist/mediasquareBidAdapter.js +1 -1
  75. package/dist/mgidBidAdapter.js +1 -1
  76. package/dist/minutemediaBidAdapter.js +1 -1
  77. package/dist/naveggIdSystem.js +1 -1
  78. package/dist/not-for-prod/prebid.js +153 -152
  79. package/dist/novatiqIdSystem.js +1 -1
  80. package/dist/oguryBidAdapter.js +1 -1
  81. package/dist/oneVideoBidAdapter.js +1 -1
  82. package/dist/onetagBidAdapter.js +1 -1
  83. package/dist/ooloAnalyticsAdapter.js +1 -1
  84. package/dist/openxAnalyticsAdapter.js +1 -1
  85. package/dist/optimonAnalyticsAdapter.js +1 -1
  86. package/dist/outbrainBidAdapter.js +1 -1
  87. package/dist/parrableIdSystem.js +1 -1
  88. package/dist/pixfutureBidAdapter.js +1 -1
  89. package/dist/prebid-core.js +3 -3
  90. package/dist/prebidmanagerAnalyticsAdapter.js +1 -1
  91. package/dist/publinkIdSystem.js +1 -1
  92. package/dist/pubmaticAnalyticsAdapter.js +1 -1
  93. package/dist/pubmaticBidAdapter.js +1 -1
  94. package/dist/pubperfAnalyticsAdapter.js +1 -1
  95. package/dist/pubstackAnalyticsAdapter.js +1 -1
  96. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  97. package/dist/pubxaiAnalyticsAdapter.js +1 -1
  98. package/dist/pulsepointAnalyticsAdapter.js +1 -1
  99. package/dist/pxyzBidAdapter.js +1 -1
  100. package/dist/quantcastBidAdapter.js +1 -1
  101. package/dist/readpeakBidAdapter.js +1 -1
  102. package/dist/realvuAnalyticsAdapter.js +1 -1
  103. package/dist/relaidoBidAdapter.js +1 -1
  104. package/dist/relevantAnalyticsAdapter.js +1 -1
  105. package/dist/rhythmoneBidAdapter.js +1 -1
  106. package/dist/riseBidAdapter.js +1 -1
  107. package/dist/rivrAnalyticsAdapter.js +1 -1
  108. package/dist/roxotAnalyticsAdapter.js +1 -1
  109. package/dist/rubiconAnalyticsAdapter.js +1 -1
  110. package/dist/rubiconBidAdapter.js +1 -1
  111. package/dist/scaleableAnalyticsAdapter.js +1 -1
  112. package/dist/seedingAllianceBidAdapter.js +1 -1
  113. package/dist/seedtagBidAdapter.js +1 -1
  114. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  115. package/dist/sharethroughBidAdapter.js +1 -1
  116. package/dist/sigmoidAnalyticsAdapter.js +1 -1
  117. package/dist/smaatoBidAdapter.js +1 -1
  118. package/dist/smartadserverBidAdapter.js +1 -1
  119. package/dist/smartxBidAdapter.js +1 -1
  120. package/dist/smilewantedBidAdapter.js +1 -1
  121. package/dist/sonobiAnalyticsAdapter.js +1 -1
  122. package/dist/sonobiBidAdapter.js +1 -1
  123. package/dist/sortableAnalyticsAdapter.js +1 -1
  124. package/dist/sortableBidAdapter.js +1 -1
  125. package/dist/sovrnAnalyticsAdapter.js +1 -1
  126. package/dist/sovrnBidAdapter.js +1 -1
  127. package/dist/sspBCBidAdapter.js +1 -1
  128. package/dist/staqAnalyticsAdapter.js +1 -1
  129. package/dist/sublimeBidAdapter.js +1 -1
  130. package/dist/synacormediaBidAdapter.js +1 -1
  131. package/dist/taboolaBidAdapter.js +1 -0
  132. package/dist/targetVideoBidAdapter.js +1 -1
  133. package/dist/teadsBidAdapter.js +1 -1
  134. package/dist/terceptAnalyticsAdapter.js +1 -1
  135. package/dist/trionBidAdapter.js +1 -1
  136. package/dist/tripleliftBidAdapter.js +1 -1
  137. package/dist/trustxBidAdapter.js +1 -1
  138. package/dist/ttdBidAdapter.js +1 -1
  139. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  140. package/dist/underdogmediaBidAdapter.js +1 -1
  141. package/dist/undertoneBidAdapter.js +1 -1
  142. package/dist/vidazooBidAdapter.js +1 -1
  143. package/dist/videobyteBidAdapter.js +1 -1
  144. package/dist/visxBidAdapter.js +1 -1
  145. package/dist/vuukleBidAdapter.js +1 -1
  146. package/dist/widespaceBidAdapter.js +1 -1
  147. package/dist/winrBidAdapter.js +1 -1
  148. package/dist/yahoosspBidAdapter.js +1 -1
  149. package/dist/yieldmoBidAdapter.js +1 -1
  150. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  151. package/dist/yuktamediaAnalyticsAdapter.js +1 -1
  152. package/dist/zeta_global_sspAnalyticsAdapter.js +1 -1
  153. package/integrationExamples/gpt/akamaidap_segments_example.html +5 -3
  154. package/modules/.submodules.json +1 -0
  155. package/modules/adlooxAnalyticsAdapter.js +9 -5
  156. package/modules/adlooxAnalyticsAdapter.md +3 -3
  157. package/modules/adlooxRtdProvider.js +3 -5
  158. package/modules/adlooxRtdProvider.md +2 -2
  159. package/modules/akamaiDapRtdProvider.js +460 -130
  160. package/modules/akamaiDapRtdProvider.md +5 -3
  161. package/modules/colossussspBidAdapter.js +17 -17
  162. package/modules/conversantBidAdapter.js +2 -2
  163. package/modules/ftrackIdSystem.js +3 -4
  164. package/modules/hadronAnalyticsAdapter.js +5 -1
  165. package/modules/koblerBidAdapter.js +54 -45
  166. package/modules/koblerBidAdapter.md +12 -17
  167. package/modules/naveggIdSystem.js +3 -2
  168. package/modules/novatiqIdSystem.js +6 -1
  169. package/modules/relaidoBidAdapter.js +0 -3
  170. package/modules/taboolaBidAdapter.js +265 -0
  171. package/modules/taboolaBidAdapter.md +49 -0
  172. package/package.json +1 -1
  173. package/src/AnalyticsAdapter.js +5 -1
  174. package/src/adloader.js +2 -1
  175. package/test/mocks/analyticsStub.js +13 -0
  176. package/test/mocks/xhr.js +22 -0
  177. package/test/spec/AnalyticsAdapter_spec.js +4 -0
  178. package/test/spec/modules/akamaiDapRtdProvider_spec.js +402 -108
  179. package/test/spec/modules/conversantBidAdapter_spec.js +36 -5
  180. package/test/spec/modules/datablocksBidAdapter_spec.js +9 -8
  181. package/test/spec/modules/fintezaAnalyticsAdapter_spec.js +4 -4
  182. package/test/spec/modules/ftrackIdSystem_spec.js +1 -9
  183. package/test/spec/modules/koblerBidAdapter_spec.js +160 -124
  184. package/test/spec/modules/naveggIdSystem_spec.js +25 -1
  185. package/test/spec/modules/proxistoreBidAdapter_spec.js +0 -2
  186. package/test/spec/modules/sovrnBidAdapter_spec.js +253 -264
  187. package/test/spec/modules/taboolaBidAdapter_spec.js +455 -0
  188. package/test/test_deps.js +1 -0
@@ -10,12 +10,22 @@ import {config} from '../src/config.js';
10
10
  import {getStorageManager} from '../src/storageManager.js';
11
11
  import {submodule} from '../src/hook.js';
12
12
  import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js';
13
+ import { loadExternalScript } from '../src/adloader.js';
13
14
 
14
15
  const MODULE_NAME = 'realTimeData';
15
16
  const SUBMODULE_NAME = 'dap';
17
+ const MODULE_CODE = 'akamaidap';
18
+
19
+ export const DAP_TOKEN = 'async_dap_token';
20
+ export const DAP_MEMBERSHIP = 'async_dap_membership';
21
+ export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership';
22
+ export const DAP_SS_ID = 'dap_ss_id';
23
+ export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds
24
+ export const DAP_MAX_RETRY_TOKENIZE = 1;
25
+ export const DAP_CLIENT_ENTROPY = 'dap_client_entropy'
16
26
 
17
- export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments';
18
27
  export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME});
28
+ let dapRetryTokenize = 0;
19
29
 
20
30
  /**
21
31
  * Lazy merge objects.
@@ -53,60 +63,92 @@ export function addRealTimeData(rtd) {
53
63
  * Real-time data retrieval from Audigent
54
64
  * @param {Object} reqBidsConfigObj
55
65
  * @param {function} onDone
56
- * @param {Object} rtdConfi
66
+ * @param {Object} rtdConfig
57
67
  * @param {Object} userConsent
58
68
  */
59
69
  export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
60
- logInfo('DEBUG(getRealTimeData) - ENTER');
70
+ let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY));
71
+ let loadScriptPromise = new Promise((resolve, reject) => {
72
+ if (rtdConfig && rtdConfig.params && rtdConfig.params.dapFpTimeout && Number.isInteger(rtdConfig.params.dapFpTimeout)) {
73
+ setTimeout(reject, rtdConfig.params.dapFpTimeout, Error('DapFP script could not be loaded'));
74
+ }
75
+ if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) {
76
+ logMessage('Using cached entropy');
77
+ resolve();
78
+ } else {
79
+ if (typeof window.dapCalculateEntropy === 'function') {
80
+ window.dapCalculateEntropy(resolve, reject);
81
+ } else {
82
+ if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapFpUrl)) {
83
+ loadExternalScript(rtdConfig.params.dapFpUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) });
84
+ } else {
85
+ reject(Error('Please check if dapFpUrl is specified and is valid under config.params'));
86
+ }
87
+ }
88
+ }
89
+ });
90
+ loadScriptPromise
91
+ .catch((error) => {
92
+ logError('Entropy could not be calculated due to: ', error.message);
93
+ })
94
+ .finally(() => {
95
+ generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent);
96
+ });
97
+ }
98
+
99
+ export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
100
+ logInfo('DEBUG(generateRealTimeData) - ENTER');
61
101
  logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname);
62
102
  logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion);
63
- let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY);
103
+ dapRetryTokenize = 0;
104
+ var jsonData = null;
105
+ if (rtdConfig && isPlainObject(rtdConfig.params)) {
106
+ if (rtdConfig.params.segtax == 504) {
107
+ let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage();
108
+ if (encMembership) {
109
+ jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax)
110
+ }
111
+ } else {
112
+ let membership = dapUtils.dapGetMembershipFromLocalStorage();
113
+ if (membership) {
114
+ jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax)
115
+ }
116
+ }
117
+ }
64
118
  if (jsonData) {
65
- let data = JSON.parse(jsonData);
66
- if (data.rtd) {
67
- addRealTimeData(data.rtd);
119
+ if (jsonData.rtd) {
120
+ addRealTimeData(jsonData.rtd);
68
121
  onDone();
69
- logInfo('DEBUG(getRealTimeData) - 1');
122
+ logInfo('DEBUG(generateRealTimeData) - 1');
70
123
  // Don't return - ensure the data is always fresh.
71
124
  }
72
125
  }
126
+ // Calling setTimeout to release the main thread so that the bid request could be sent.
127
+ setTimeout(callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent);
128
+ }
73
129
 
130
+ function callDapAPIs(bidConfig, onDone, rtdConfig, userConsent) {
74
131
  if (rtdConfig && isPlainObject(rtdConfig.params)) {
75
132
  let config = {
76
133
  api_hostname: rtdConfig.params.apiHostname,
77
134
  api_version: rtdConfig.params.apiVersion,
78
135
  domain: rtdConfig.params.domain,
79
- segtax: rtdConfig.params.segtax
80
- };
81
- let identity = {
82
- type: rtdConfig.params.identityType
136
+ segtax: rtdConfig.params.segtax,
137
+ identity: {type: rtdConfig.params.identityType}
83
138
  };
84
- let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl);
85
- if (token !== null) {
86
- let membership = dapUtils.dapGetMembership(config, token);
87
- let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config);
88
- logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment);
89
- let data = {
90
- rtd: {
91
- ortb2: {
92
- user: {
93
- data: [
94
- udSegment
95
- ]
96
- },
97
- site: {
98
- ext: {
99
- data: {
100
- dapSAID: membership.said
101
- }
102
- }
103
- }
104
- }
105
- }
106
- };
107
- storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data));
108
- onDone();
139
+ let refreshMembership = true;
140
+ let token = dapUtils.dapGetTokenFromLocalStorage();
141
+ logMessage('token is: ', token);
142
+ if (token !== null) { // If token is not null then check the membership in storage and add the RTD object
143
+ if (config.segtax == 504) { // Follow the encrypted membership path
144
+ dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server
145
+ refreshMembership = false;
146
+ } else {
147
+ dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server
148
+ refreshMembership = false;
149
+ }
109
150
  }
151
+ dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases
110
152
  }
111
153
  }
112
154
 
@@ -117,6 +159,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
117
159
  * @return {boolean}
118
160
  */
119
161
  function init(provider, userConsent) {
162
+ if (dapUtils.checkConsent(userConsent) === false) {
163
+ return false;
164
+ }
120
165
  return true;
121
166
  }
122
167
 
@@ -128,104 +173,190 @@ export const akamaiDapRtdSubmodule = {
128
173
  };
129
174
 
130
175
  submodule(MODULE_NAME, akamaiDapRtdSubmodule);
131
-
132
176
  export const dapUtils = {
177
+ dapGetEntropy: function(resolve, reject) {
178
+ if (typeof window.dapCalculateEntropy === 'function') {
179
+ window.dapCalculateEntropy(resolve, reject);
180
+ } else {
181
+ reject(Error('window.dapCalculateEntropy function is not defined'))
182
+ }
183
+ },
133
184
 
134
- dapGetToken: function(config, identity, ttl) {
185
+ dapGetTokenFromLocalStorage: function(ttl) {
135
186
  let now = Math.round(Date.now() / 1000.0); // in seconds
136
- let storageName = 'async_dap_token';
137
187
  let token = null;
138
-
139
- if (ttl == 0) {
140
- localStorage.removeItem(storageName);
188
+ let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN));
189
+ if (item) {
190
+ if (now < item.expires_at) {
191
+ token = item.token;
192
+ }
141
193
  }
194
+ return token;
195
+ },
142
196
 
143
- let item = JSON.parse(localStorage.getItem(storageName));
144
- if (item == null) {
145
- item = {
146
- expires_at: now - 1,
147
- token: null
148
- };
149
- } else {
150
- token = item.token;
151
- }
152
-
153
- if (now > item.expires_at) {
154
- dapUtils.dapLog('Token missing or expired, fetching a new one...');
155
- // Trigger a refresh
156
- let configAsync = {...config};
157
- dapUtils.dapTokenize(configAsync, identity,
158
- function(token, status, xhr) {
159
- item.expires_at = now + ttl;
160
- item.token = token;
161
- localStorage.setItem(storageName, JSON.stringify(item));
162
- dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds');
163
- let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100');
164
- if (deviceId100 != null) {
165
- localStorage.setItem('dap_deviceId100', deviceId100);
166
- dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100);
197
+ dapRefreshToken: function(config, refreshMembership, onDone) {
198
+ dapUtils.dapLog('Token missing or expired, fetching a new one...');
199
+ // Trigger a refresh
200
+ let now = Math.round(Date.now() / 1000.0); // in seconds
201
+ let item = {}
202
+ let configAsync = {...config};
203
+ dapUtils.dapTokenize(configAsync, config.identity, onDone,
204
+ function(token, status, xhr, onDone) {
205
+ item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
206
+ let exp = dapUtils.dapExtractExpiryFromToken(token)
207
+ if (typeof exp == 'number') {
208
+ item.expires_at = exp - 10;
209
+ }
210
+ item.token = token;
211
+ storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item));
212
+ dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at);
213
+ let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID');
214
+ if (dapSSID) {
215
+ storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID));
216
+ }
217
+ let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100');
218
+ if (deviceId100 != null) {
219
+ storage.setDataInLocalStorage('dap_deviceId100', deviceId100);
220
+ dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100);
221
+ }
222
+ if (refreshMembership) {
223
+ if (config.segtax == 504) {
224
+ dapUtils.dapRefreshEncryptedMembership(config, token, onDone);
225
+ } else {
226
+ dapUtils.dapRefreshMembership(config, token, onDone);
167
227
  }
168
- },
169
- function(xhr, status, error) {
170
- logError('ERROR(' + error + '): failed to retrieve token! ' + status);
171
228
  }
172
- );
173
- }
174
-
175
- return token;
229
+ },
230
+ function(xhr, status, error, onDone) {
231
+ logError('ERROR(' + error + '): failed to retrieve token! ' + status);
232
+ onDone()
233
+ }
234
+ );
176
235
  },
177
236
 
178
- dapGetMembership: function(config, token) {
237
+ dapGetMembershipFromLocalStorage: function() {
179
238
  let now = Math.round(Date.now() / 1000.0); // in seconds
180
- let storageName = 'async_dap_membership';
181
- let maxTtl = 3600; // if the cached membership is older than this, return null
182
239
  let membership = null;
183
- let item = JSON.parse(localStorage.getItem(storageName));
184
- if (item == null || (now - item.expires_at) > maxTtl) {
185
- item = {
186
- expires_at: now - 1,
187
- said: null,
188
- cohorts: null,
189
- attributes: null
190
- };
191
- } else {
192
- membership = {
193
- said: item.said,
194
- cohorts: item.cohorts,
195
- attributes: null
196
- };
240
+ let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP));
241
+ if (item) {
242
+ if (now < item.expires_at) {
243
+ membership = {
244
+ said: item.said,
245
+ cohorts: item.cohorts,
246
+ attributes: null
247
+ };
248
+ }
197
249
  }
250
+ return membership;
251
+ },
198
252
 
199
- // Always refresh the cached membership.
253
+ dapRefreshMembership: function(config, token, onDone) {
254
+ let now = Math.round(Date.now() / 1000.0); // in seconds
255
+ let item = {}
200
256
  let configAsync = {...config};
201
- dapUtils.dapMembership(configAsync, token,
202
- function(membership, status, xhr) {
203
- item.expires_at = now + maxTtl;
257
+ dapUtils.dapMembership(configAsync, token, onDone,
258
+ function(membership, status, xhr, onDone) {
259
+ item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
260
+ let exp = dapUtils.dapExtractExpiryFromToken(membership.said)
261
+ if (typeof exp == 'number') {
262
+ item.expires_at = exp - 10;
263
+ }
204
264
  item.said = membership.said;
205
265
  item.cohorts = membership.cohorts;
206
- localStorage.setItem(storageName, JSON.stringify(item));
266
+ storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item));
207
267
  dapUtils.dapLog('Successfully updated and stored membership:');
208
268
  dapUtils.dapLog(item);
269
+
270
+ let data = dapUtils.dapGetRtdObj(item, config.segtax)
271
+ dapUtils.checkAndAddRealtimeData(data, config.segtax);
272
+ onDone();
209
273
  },
210
- function(xhr, status, error) {
274
+ function(xhr, status, error, onDone) {
211
275
  logError('ERROR(' + error + '): failed to retrieve membership! ' + status);
276
+ if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) {
277
+ dapRetryTokenize++;
278
+ dapUtils.dapRefreshToken(config, true, onDone);
279
+ } else {
280
+ onDone();
281
+ }
212
282
  }
213
283
  );
284
+ },
214
285
 
215
- return membership;
286
+ dapGetEncryptedMembershipFromLocalStorage: function() {
287
+ let now = Math.round(Date.now() / 1000.0); // in seconds
288
+ let encMembership = null;
289
+ let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP));
290
+ if (item) {
291
+ if (now < item.expires_at) {
292
+ encMembership = {
293
+ encryptedSegments: item.encryptedSegments
294
+ };
295
+ }
296
+ }
297
+ return encMembership;
298
+ },
299
+
300
+ dapRefreshEncryptedMembership: function(config, token, onDone) {
301
+ let now = Math.round(Date.now() / 1000.0); // in seconds
302
+ let item = {};
303
+ let configAsync = {...config};
304
+ dapUtils.dapEncryptedMembership(configAsync, token, onDone,
305
+ function(encToken, status, xhr, onDone) {
306
+ item.expires_at = now + DAP_DEFAULT_TOKEN_TTL;
307
+ let exp = dapUtils.dapExtractExpiryFromToken(encToken)
308
+ if (typeof exp == 'number') {
309
+ item.expires_at = exp - 10;
310
+ }
311
+ item.encryptedSegments = encToken;
312
+ storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item));
313
+ dapUtils.dapLog('Successfully updated and stored encrypted membership:');
314
+ dapUtils.dapLog(item);
315
+
316
+ let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax);
317
+ dapUtils.checkAndAddRealtimeData(encData, config.segtax);
318
+ onDone();
319
+ },
320
+ function(xhr, status, error, onDone) {
321
+ logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status);
322
+ if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) {
323
+ dapRetryTokenize++;
324
+ dapUtils.dapRefreshToken(config, true, onDone);
325
+ } else {
326
+ onDone();
327
+ }
328
+ }
329
+ );
330
+ },
331
+
332
+ /**
333
+ * DESCRIPTION
334
+ * Extract expiry value from a token
335
+ */
336
+ dapExtractExpiryFromToken: function(token) {
337
+ let exp = null;
338
+ if (token) {
339
+ const tokenArray = token.split('..');
340
+ if (tokenArray && tokenArray.length > 0) {
341
+ let decode = atob(tokenArray[0])
342
+ let header = JSON.parse(decode.replace(/&quot;/g, '"'));
343
+ exp = header.exp;
344
+ }
345
+ }
346
+ return exp
216
347
  },
217
348
 
218
349
  /**
219
350
  * DESCRIPTION
220
351
  *
221
352
  * Convert a DAP membership response to an OpenRTB2 segment object suitable
222
- * for insertion into user.data.segment or site.data.segment.
353
+ * for insertion into user.data.segment or site.data.segment and add it to the rtd obj.
223
354
  */
224
- dapMembershipToRtbSegment: function(membership, config) {
355
+ dapGetRtdObj: function(membership, segtax) {
225
356
  let segment = {
226
357
  name: 'dap.akamai.com',
227
358
  ext: {
228
- 'segtax': config.segtax
359
+ 'segtax': segtax
229
360
  },
230
361
  segment: []
231
362
  };
@@ -234,7 +365,83 @@ export const dapUtils = {
234
365
  segment.segment.push({ id: i });
235
366
  }
236
367
  }
237
- return segment;
368
+ let data = {
369
+ rtd: {
370
+ ortb2: {
371
+ user: {
372
+ data: [
373
+ segment
374
+ ]
375
+ },
376
+ site: {
377
+ ext: {
378
+ data: {
379
+ dapSAID: membership.said
380
+ }
381
+ }
382
+ }
383
+ }
384
+ }
385
+ };
386
+ return data;
387
+ },
388
+
389
+ /**
390
+ * DESCRIPTION
391
+ *
392
+ * Convert a DAP membership response to an OpenRTB2 segment object suitable
393
+ * for insertion into user.data.segment or site.data.segment and add it to the rtd obj.
394
+ */
395
+ dapGetEncryptedRtdObj: function(encToken, segtax) {
396
+ let segment = {
397
+ name: 'dap.akamai.com',
398
+ ext: {
399
+ 'segtax': segtax
400
+ },
401
+ segment: []
402
+ };
403
+ if (encToken != null) {
404
+ segment.segment.push({ id: encToken.encryptedSegments });
405
+ }
406
+ let encData = {
407
+ rtd: {
408
+ ortb2: {
409
+ user: {
410
+ data: [
411
+ segment
412
+ ]
413
+ }
414
+ }
415
+ }
416
+ };
417
+ return encData;
418
+ },
419
+
420
+ checkAndAddRealtimeData: function(data, segtax) {
421
+ if (data.rtd) {
422
+ if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) {
423
+ logMessage('DEBUG(handleInit): rtb Object already added');
424
+ } else {
425
+ addRealTimeData(data.rtd);
426
+ }
427
+ logInfo('DEBUG(checkAndAddRealtimeData) - 1');
428
+ }
429
+ },
430
+
431
+ checkIfSegmentsAlreadyExist: function(rtd, segtax) {
432
+ let segmentsExist = false
433
+ let ortb2 = config.getConfig('ortb2') || {};
434
+ if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) {
435
+ for (let i = 0; i < ortb2.user.data.length; i++) {
436
+ let element = ortb2.user.data[i]
437
+ if (element.ext && element.ext.segtax == segtax) {
438
+ segmentsExist = true
439
+ logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data);
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ return segmentsExist
238
445
  },
239
446
 
240
447
  dapLog: function(args) {
@@ -248,6 +455,35 @@ export const dapUtils = {
248
455
  logInfo('%cDAP Client', css, args);
249
456
  },
250
457
 
458
+ isValidHttpsUrl: function(urlString) {
459
+ let url;
460
+ try {
461
+ url = new URL(urlString);
462
+ } catch (_) {
463
+ return false;
464
+ }
465
+ return url.protocol === 'https:';
466
+ },
467
+
468
+ checkConsent: function(userConsent) {
469
+ let consent = true;
470
+
471
+ if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) {
472
+ const gdpr = userConsent.gdpr;
473
+ const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0;
474
+ const gdprConsentString = hasGdpr ? gdpr.consentString : '';
475
+ if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) {
476
+ logError('akamaiDapRtd submodule requires consent string to call API');
477
+ consent = false;
478
+ }
479
+ } else if (userConsent && userConsent.usp) {
480
+ const usp = userConsent.usp;
481
+ consent = usp[1] !== 'N' && usp[2] !== 'Y';
482
+ }
483
+
484
+ return consent;
485
+ },
486
+
251
487
  /*******************************************************************************
252
488
  *
253
489
  * V2 (And Beyond) API
@@ -293,23 +529,23 @@ export const dapUtils = {
293
529
  * function( response, status, xhr } { token = response; },
294
530
  * function( xhr, status, error ) { ; } // handle error
295
531
  */
296
- dapTokenize: function(config, identity, onSuccess = null, onError = null) {
532
+ dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) {
297
533
  if (onError == null) {
298
- onError = function(xhr, status, error) {};
534
+ onError = function(xhr, status, error, onDone) {};
299
535
  }
300
536
 
301
537
  if (config == null || typeof (config) == typeof (undefined)) {
302
- onError(null, 'Invalid config object', 'ClientError');
538
+ onError(null, 'Invalid config object', 'ClientError', onDone);
303
539
  return;
304
540
  }
305
541
 
306
542
  if (typeof (config.domain) != 'string') {
307
- onError(null, 'Invalid config.domain: must be a string', 'ClientError');
543
+ onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone);
308
544
  return;
309
545
  }
310
546
 
311
547
  if (config.domain.length <= 0) {
312
- onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError');
548
+ onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone);
313
549
  return;
314
550
  }
315
551
 
@@ -318,22 +554,22 @@ export const dapUtils = {
318
554
  }
319
555
 
320
556
  if (typeof (config.api_version) != 'string') {
321
- onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError');
557
+ onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone);
322
558
  return;
323
559
  }
324
560
 
325
561
  if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
326
- onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError');
562
+ onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone);
327
563
  return;
328
564
  }
329
565
 
330
566
  if (identity == null || typeof (identity) == typeof (undefined)) {
331
- onError(null, 'Invalid identity object', 'ClientError');
567
+ onError(null, 'Invalid identity object', 'ClientError', onDone);
332
568
  return;
333
569
  }
334
570
 
335
571
  if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) {
336
- onError(null, "Identity must contain a valid 'type' field", 'ClientError');
572
+ onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone);
337
573
  return;
338
574
  }
339
575
 
@@ -348,6 +584,11 @@ export const dapUtils = {
348
584
  apiParams.attributes = identity.attributes;
349
585
  }
350
586
 
587
+ let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY));
588
+ if (entropyDict && entropyDict.entropy) {
589
+ apiParams.entropy = entropyDict.entropy;
590
+ }
591
+
351
592
  let method;
352
593
  let body;
353
594
  let path;
@@ -359,10 +600,16 @@ export const dapUtils = {
359
600
  body = JSON.stringify(apiParams);
360
601
  break;
361
602
  default:
362
- onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError');
603
+ onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone);
363
604
  return;
364
605
  }
365
606
 
607
+ let customHeaders = {'Content-Type': 'application/json'};
608
+ let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID));
609
+ if (dapSSID) {
610
+ customHeaders['Akamai-DAP-SS-ID'] = dapSSID;
611
+ }
612
+
366
613
  let url = 'https://' + config.api_hostname + path;
367
614
  let cb = {
368
615
  success: (response, request) => {
@@ -373,19 +620,16 @@ export const dapUtils = {
373
620
  token = request.getResponseHeader('Akamai-DAP-Token');
374
621
  break;
375
622
  }
376
- onSuccess(token, request.status, request);
623
+ onSuccess(token, request.status, request, onDone);
377
624
  },
378
625
  error: (request, error) => {
379
- onError(request, request.statusText, error);
626
+ onError(request, request.statusText, error, onDone);
380
627
  }
381
628
  };
382
629
 
383
630
  ajax(url, cb, body, {
384
631
  method: method,
385
- customHeaders: {
386
- 'Content-Type': 'application/json',
387
- 'Pragma': 'akamai-x-cache-on'
388
- }
632
+ customHeaders: customHeaders
389
633
  });
390
634
  },
391
635
 
@@ -411,7 +655,7 @@ export const dapUtils = {
411
655
  * api_hostname: 'api.dap.akadns.net',
412
656
  * };
413
657
  *
414
- * // token from dap_x1_tokenize
658
+ * // token from dap_tokenize
415
659
  *
416
660
  * dapMembership( config, token,
417
661
  * function( membership, status, xhr ) {
@@ -422,13 +666,13 @@ export const dapUtils = {
422
666
  * } );
423
667
  *
424
668
  */
425
- dapMembership: function(config, token, onSuccess = null, onError = null) {
669
+ dapMembership: function(config, token, onDone, onSuccess = null, onError = null) {
426
670
  if (onError == null) {
427
- onError = function(xhr, status, error) {};
671
+ onError = function(xhr, status, error, onDone) {};
428
672
  }
429
673
 
430
674
  if (config == null || typeof (config) == typeof (undefined)) {
431
- onError(null, 'Invalid config object', 'ClientError');
675
+ onError(null, 'Invalid config object', 'ClientError', onDone);
432
676
  return;
433
677
  }
434
678
 
@@ -437,32 +681,32 @@ export const dapUtils = {
437
681
  }
438
682
 
439
683
  if (typeof (config.api_version) != 'string') {
440
- onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError');
684
+ onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone);
441
685
  return;
442
686
  }
443
687
 
444
688
  if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
445
- onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError');
689
+ onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone);
446
690
  return;
447
691
  }
448
692
 
449
693
  if (token == null || typeof (token) != 'string') {
450
- onError(null, 'Invalid token: must be a non-null string', 'ClientError');
694
+ onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone);
451
695
  return;
452
696
  }
453
697
  let path = '/data-activation/' +
454
- config.api_version +
455
- '/token/' + token +
456
- '/membership';
698
+ config.api_version +
699
+ '/token/' + token +
700
+ '/membership';
457
701
 
458
702
  let url = 'https://' + config.api_hostname + path;
459
703
 
460
704
  let cb = {
461
705
  success: (response, request) => {
462
- onSuccess(JSON.parse(response), request.status, request);
706
+ onSuccess(JSON.parse(response), request.status, request, onDone);
463
707
  },
464
708
  error: (error, request) => {
465
- onError(request, request.status, error);
709
+ onError(request, request.status, error, onDone);
466
710
  }
467
711
  };
468
712
 
@@ -470,5 +714,91 @@ export const dapUtils = {
470
714
  method: 'GET',
471
715
  customHeaders: {}
472
716
  });
717
+ },
718
+
719
+ /**
720
+ * SYNOPSIS
721
+ *
722
+ * dapEncryptedMembership( config, token, onSuccess, onError );
723
+ *
724
+ * DESCRIPTION
725
+ *
726
+ * Return the audience segment membership along with a new Secure Advertising
727
+ * ID for this token in encrypted format.
728
+ *
729
+ * PARAMETERS
730
+ *
731
+ * config: an array of system configuration parameters
732
+ *
733
+ * token: the token previously returned from the tokenize API
734
+ *
735
+ * EXAMPLE
736
+ *
737
+ * config = {
738
+ * api_hostname: 'api.dap.akadns.net',
739
+ * };
740
+ *
741
+ * // token from dap_tokenize
742
+ *
743
+ * dapEncryptedMembership( config, token,
744
+ * function( membership, status, xhr ) {
745
+ * // Run auction with membership.segments and membership.said after decryption
746
+ * },
747
+ * function( xhr, status, error ) {
748
+ * // error
749
+ * } );
750
+ *
751
+ */
752
+ dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) {
753
+ if (onError == null) {
754
+ onError = function(xhr, status, error, onDone) {};
755
+ }
756
+
757
+ if (config == null || typeof (config) == typeof (undefined)) {
758
+ onError(null, 'Invalid config object', 'ClientError', onDone);
759
+ return;
760
+ }
761
+
762
+ if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) {
763
+ config.api_version = 'x1';
764
+ }
765
+
766
+ if (typeof (config.api_version) != 'string') {
767
+ onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone);
768
+ return;
769
+ }
770
+
771
+ if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
772
+ onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone);
773
+ return;
774
+ }
775
+
776
+ if (token == null || typeof (token) != 'string') {
777
+ onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone);
778
+ return;
779
+ }
780
+ let path = '/data-activation/' +
781
+ config.api_version +
782
+ '/token/' + token +
783
+ '/membership/encrypt';
784
+
785
+ let url = 'https://' + config.api_hostname + path;
786
+
787
+ let cb = {
788
+ success: (response, request) => {
789
+ let encToken = request.getResponseHeader('Akamai-DAP-Token');
790
+ onSuccess(encToken, request.status, request, onDone);
791
+ },
792
+ error: (error, request) => {
793
+ onError(request, request.status, error, onDone);
794
+ }
795
+ };
796
+ ajax(url, cb, undefined, {
797
+ method: 'GET',
798
+ customHeaders: {
799
+ 'Content-Type': 'application/json',
800
+ 'Pragma': 'akamai-x-get-extracted-values'
801
+ }
802
+ });
473
803
  }
474
804
  }