prebid.js 9.26.0 → 9.28.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 (305) hide show
  1. package/creative/renderers/native/renderer.js +29 -8
  2. package/dist/33acrossAnalyticsAdapter.js +1 -1
  3. package/dist/33acrossBidAdapter.js +1 -1
  4. package/dist/33acrossIdSystem.js +1 -1
  5. package/dist/BTBidAdapter.js +1 -1
  6. package/dist/acuityadsBidAdapter.js +1 -1
  7. package/dist/adagioAnalyticsAdapter.js +1 -1
  8. package/dist/adagioBidAdapter.js +1 -1
  9. package/dist/adagioRtdProvider.js +1 -1
  10. package/dist/adagioUtils.js +1 -1
  11. package/dist/addefendBidAdapter.js +1 -1
  12. package/dist/adgenerationBidAdapter.js +1 -1
  13. package/dist/adlooxAdServerVideo.js +1 -1
  14. package/dist/adlooxAnalyticsAdapter.js +1 -1
  15. package/dist/adlooxRtdProvider.js +1 -1
  16. package/dist/adqueryBidAdapter.js +1 -1
  17. package/dist/adrelevantisBidAdapter.js +1 -1
  18. package/dist/adstirBidAdapter.js +1 -1
  19. package/dist/adtrgtmeBidAdapter.js +1 -1
  20. package/dist/adxcgAnalyticsAdapter.js +1 -1
  21. package/dist/adxcgBidAdapter.js +1 -1
  22. package/dist/adyoulikeBidAdapter.js +1 -1
  23. package/dist/agmaAnalyticsAdapter.js +1 -1
  24. package/dist/ajaBidAdapter.js +1 -1
  25. package/dist/akamaiDapRtdProvider.js +1 -1
  26. package/dist/amxBidAdapter.js +1 -1
  27. package/dist/amxIdSystem.js +1 -1
  28. package/dist/aniviewBidAdapter.js +1 -1
  29. package/dist/appierAnalyticsAdapter.js +1 -1
  30. package/dist/appnexusBidAdapter.js +1 -1
  31. package/dist/asoBidAdapter.js +1 -1
  32. package/dist/axonixBidAdapter.js +1 -1
  33. package/dist/bidglassBidAdapter.js +1 -1
  34. package/dist/big-richmediaBidAdapter.js +1 -1
  35. package/dist/bitmediaBidAdapter.js +1 -1
  36. package/dist/blueBidAdapter.js +1 -0
  37. package/dist/bridBidAdapter.js +1 -1
  38. package/dist/bridgeuppBidAdapter.js +1 -1
  39. package/dist/bridgewellBidAdapter.js +1 -1
  40. package/dist/brightMountainMediaBidAdapter.js +1 -1
  41. package/dist/carodaBidAdapter.js +1 -1
  42. package/dist/chtnwBidAdapter.js +1 -1
  43. package/dist/chunk-core.js +1 -1
  44. package/dist/conceptxBidAdapter.js +1 -1
  45. package/dist/concertBidAdapter.js +1 -1
  46. package/dist/connectadBidAdapter.js +1 -1
  47. package/dist/consumableBidAdapter.js +1 -1
  48. package/dist/contxtfulBidAdapter.js +1 -1
  49. package/dist/contxtfulRtdProvider.js +1 -1
  50. package/dist/conversantAnalyticsAdapter.js +1 -1
  51. package/dist/conversantBidAdapter.js +1 -1
  52. package/dist/craftBidAdapter.js +1 -1
  53. package/dist/creative-renderer-native.js +1 -1
  54. package/dist/criteoBidAdapter.js +1 -1
  55. package/dist/cwireBidAdapter.js +1 -1
  56. package/dist/dailymotionBidAdapter.js +1 -1
  57. package/dist/dependencies.json +7 -1
  58. package/dist/dspxBidAdapter.js +1 -1
  59. package/dist/dxkultureBidAdapter.js +1 -1
  60. package/dist/eplanningBidAdapter.js +1 -1
  61. package/dist/equativBidAdapter.js +1 -1
  62. package/dist/escalaxBidAdapter.js +1 -1
  63. package/dist/eskimiBidAdapter.js +1 -1
  64. package/dist/euidIdSystem.js +1 -1
  65. package/dist/exadsBidAdapter.js +1 -1
  66. package/dist/feedadBidAdapter.js +1 -1
  67. package/dist/finativeBidAdapter.js +1 -1
  68. package/dist/freewheel-sspBidAdapter.js +1 -1
  69. package/dist/gmosspBidAdapter.js +1 -1
  70. package/dist/greenbidsAnalyticsAdapter.js +1 -1
  71. package/dist/greenbidsBidAdapter.js +1 -1
  72. package/dist/greenbidsRtdProvider.js +1 -1
  73. package/dist/gridBidAdapter.js +1 -1
  74. package/dist/gumgumBidAdapter.js +1 -1
  75. package/dist/h12mediaBidAdapter.js +1 -1
  76. package/dist/hypelabBidAdapter.js +1 -1
  77. package/dist/id5AnalyticsAdapter.js +1 -1
  78. package/dist/id5IdSystem.js +1 -1
  79. package/dist/imdsBidAdapter.js +1 -1
  80. package/dist/improvedigitalBidAdapter.js +1 -1
  81. package/dist/inmobiBidAdapter.js +1 -1
  82. package/dist/insticatorBidAdapter.js +1 -1
  83. package/dist/intentIqAnalyticsAdapter.js +1 -1
  84. package/dist/intentIqConstants.js +1 -1
  85. package/dist/ixBidAdapter.js +1 -1
  86. package/dist/jixieBidAdapter.js +1 -1
  87. package/dist/justpremiumBidAdapter.js +1 -1
  88. package/dist/jwplayerVideoProvider.js +1 -1
  89. package/dist/kargoBidAdapter.js +1 -1
  90. package/dist/kimberliteBidAdapter.js +1 -1
  91. package/dist/konduitAnalyticsAdapter.js +1 -1
  92. package/dist/kueezBidAdapter.js +1 -1
  93. package/dist/lassoBidAdapter.js +1 -1
  94. package/dist/lifestreetBidAdapter.js +1 -1
  95. package/dist/liveIntentId.js +1 -1
  96. package/dist/liveIntentIdSystem.js +1 -1
  97. package/dist/liveIntentRtdProvider.js +1 -0
  98. package/dist/logicadBidAdapter.js +1 -1
  99. package/dist/loglyliftBidAdapter.js +1 -1
  100. package/dist/luceadBidAdapter.js +1 -1
  101. package/dist/mabidderBidAdapter.js +1 -1
  102. package/dist/madsenseBidAdapter.js +1 -1
  103. package/dist/magniteAnalyticsAdapter.js +1 -1
  104. package/dist/malltvAnalyticsAdapter.js +1 -1
  105. package/dist/marsmediaBidAdapter.js +1 -1
  106. package/dist/mediafuseBidAdapter.js +1 -1
  107. package/dist/medianetAnalyticsAdapter.js +1 -1
  108. package/dist/medianetBidAdapter.js +1 -1
  109. package/dist/mediasquareBidAdapter.js +1 -1
  110. package/dist/mgidBidAdapter.js +1 -1
  111. package/dist/minutemediaBidAdapter.js +1 -1
  112. package/dist/missenaBidAdapter.js +1 -1
  113. package/dist/mspa.js +1 -1
  114. package/dist/nativeRendering.js +1 -1
  115. package/dist/nextMillenniumBidAdapter.js +1 -1
  116. package/dist/nexx360BidAdapter.js +1 -1
  117. package/dist/nobidAnalyticsAdapter.js +1 -1
  118. package/dist/nobidBidAdapter.js +1 -1
  119. package/dist/not-for-prod/prebid.js +193 -191
  120. package/dist/oguryBidAdapter.js +1 -1
  121. package/dist/omsBidAdapter.js +1 -1
  122. package/dist/onetagBidAdapter.js +1 -1
  123. package/dist/ooloAnalyticsAdapter.js +1 -1
  124. package/dist/openwebBidAdapter.js +1 -1
  125. package/dist/openxBidAdapter.js +1 -1
  126. package/dist/optidigitalBidAdapter.js +1 -1
  127. package/dist/orbidderBidAdapter.js +1 -1
  128. package/dist/orbitsoftBidAdapter.js +1 -1
  129. package/dist/outbrainBidAdapter.js +1 -1
  130. package/dist/pixfutureBidAdapter.js +1 -1
  131. package/dist/publinkIdSystem.js +1 -1
  132. package/dist/pubmaticAnalyticsAdapter.js +1 -1
  133. package/dist/pubmaticBidAdapter.js +1 -1
  134. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  135. package/dist/pubxaiAnalyticsAdapter.js +1 -1
  136. package/dist/pxyzBidAdapter.js +1 -1
  137. package/dist/quantcastBidAdapter.js +1 -1
  138. package/dist/readpeakBidAdapter.js +1 -1
  139. package/dist/relaidoBidAdapter.js +1 -1
  140. package/dist/retailspotBidAdapter.js +1 -1
  141. package/dist/rhythmoneBidAdapter.js +1 -1
  142. package/dist/riseBidAdapter.js +1 -1
  143. package/dist/riseUtils.js +1 -1
  144. package/dist/rubiconBidAdapter.js +1 -1
  145. package/dist/seedingAllianceBidAdapter.js +1 -1
  146. package/dist/seedtagBidAdapter.js +1 -1
  147. package/dist/sharedIdSystem.js +1 -1
  148. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  149. package/dist/sharethroughBidAdapter.js +1 -1
  150. package/dist/shinezBidAdapter.js +1 -1
  151. package/dist/showheroes-bsBidAdapter.js +1 -1
  152. package/dist/sizeUtils.js +1 -1
  153. package/dist/smaatoBidAdapter.js +1 -1
  154. package/dist/smartadserverBidAdapter.js +1 -1
  155. package/dist/smartxBidAdapter.js +1 -1
  156. package/dist/smilewantedBidAdapter.js +1 -1
  157. package/dist/snigelBidAdapter.js +1 -1
  158. package/dist/sonobiBidAdapter.js +1 -1
  159. package/dist/sovrnBidAdapter.js +1 -1
  160. package/dist/sparteoBidAdapter.js +1 -1
  161. package/dist/sspBCBidAdapter.js +1 -1
  162. package/dist/stnBidAdapter.js +1 -1
  163. package/dist/stvBidAdapter.js +1 -1
  164. package/dist/sublimeBidAdapter.js +1 -1
  165. package/dist/symitriDapRtdProvider.js +1 -1
  166. package/dist/taboolaBidAdapter.js +1 -1
  167. package/dist/tappxBidAdapter.js +1 -1
  168. package/dist/targetVideoBidAdapter.js +1 -1
  169. package/dist/teadsBidAdapter.js +1 -1
  170. package/dist/terceptAnalyticsAdapter.js +1 -1
  171. package/dist/themoneytizerBidAdapter.js +1 -1
  172. package/dist/trionBidAdapter.js +1 -1
  173. package/dist/tripleliftBidAdapter.js +1 -1
  174. package/dist/ttdBidAdapter.js +1 -1
  175. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  176. package/dist/uid2IdSystem.js +1 -1
  177. package/dist/underdogmediaBidAdapter.js +1 -1
  178. package/dist/undertoneBidAdapter.js +1 -1
  179. package/dist/unrulyBidAdapter.js +1 -1
  180. package/dist/userId.js +1 -1
  181. package/dist/vdoaiBidAdapter.js +1 -1
  182. package/dist/vidazooUtils.js +1 -1
  183. package/dist/videobyteBidAdapter.js +1 -1
  184. package/dist/visxBidAdapter.js +1 -1
  185. package/dist/vuukleBidAdapter.js +1 -1
  186. package/dist/widespaceBidAdapter.js +1 -1
  187. package/dist/winrBidAdapter.js +1 -1
  188. package/dist/wurflRtdProvider.js +1 -1
  189. package/dist/yahooAdsBidAdapter.js +1 -1
  190. package/dist/yieldlabBidAdapter.js +1 -1
  191. package/dist/yieldmoBidAdapter.js +1 -1
  192. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  193. package/dist/zeta_global_sspAnalyticsAdapter.js +1 -1
  194. package/integrationExamples/gpt/liveIntentRtdProviderExample.html +164 -0
  195. package/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html +3 -1
  196. package/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html +2 -0
  197. package/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html +2 -0
  198. package/integrationExamples/videoModule/jwplayer/eventListeners.html +2 -0
  199. package/integrationExamples/videoModule/jwplayer/eventsUI.html +2 -0
  200. package/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html +2 -0
  201. package/integrationExamples/videoModule/jwplayer/mediaMetadata.html +3 -1
  202. package/integrationExamples/videoModule/jwplayer/playlist.html +3 -1
  203. package/libraries/creative-renderer-native/renderer.js +1 -1
  204. package/libraries/intentIqConstants/intentIqConstants.js +1 -1
  205. package/libraries/liveIntentId/shared.js +18 -5
  206. package/libraries/mspa/activityControls.js +26 -7
  207. package/libraries/riseUtils/constants.js +20 -0
  208. package/libraries/riseUtils/index.js +143 -38
  209. package/libraries/sizeUtils/sizeUtils.js +25 -0
  210. package/libraries/vidazooUtils/bidderUtils.js +2 -0
  211. package/modules/33acrossIdSystem.js +64 -23
  212. package/modules/33acrossIdSystem.md +2 -1
  213. package/modules/acuityadsBidAdapter.js +1 -1
  214. package/modules/adagioRtdProvider.js +15 -9
  215. package/modules/adlooxAnalyticsAdapter.js +12 -6
  216. package/modules/blueBidAdapter.js +122 -0
  217. package/modules/blueBidAdapter.md +28 -0
  218. package/modules/conceptxBidAdapter.js +2 -0
  219. package/modules/consumableBidAdapter.js +3 -1
  220. package/modules/contxtfulBidAdapter.js +25 -13
  221. package/modules/contxtfulRtdProvider.js +9 -12
  222. package/modules/eplanningBidAdapter.js +5 -1
  223. package/modules/equativBidAdapter.js +13 -18
  224. package/modules/escalaxBidAdapter.js +2 -2
  225. package/modules/eskimiBidAdapter.js +4 -1
  226. package/modules/improvedigitalBidAdapter.js +0 -67
  227. package/modules/intentIqAnalyticsAdapter.js +1 -1
  228. package/modules/ixBidAdapter.js +7 -1
  229. package/modules/jwplayerVideoProvider.js +99 -6
  230. package/modules/liveIntentRtdProvider.js +52 -0
  231. package/modules/liveIntentRtdProvider.md +45 -0
  232. package/modules/minutemediaBidAdapter.js +6 -80
  233. package/modules/minutemediaBidAdapter.md +1 -1
  234. package/modules/missenaBidAdapter.js +2 -0
  235. package/modules/nativeRendering.js +2 -1
  236. package/modules/omsBidAdapter.js +25 -11
  237. package/modules/omsBidAdapter.md +15 -0
  238. package/modules/openwebBidAdapter.js +6 -81
  239. package/modules/openwebBidAdapter.md +1 -1
  240. package/modules/openxBidAdapter.js +19 -6
  241. package/modules/openxBidAdapter.md +46 -1
  242. package/modules/orbitsoftBidAdapter.js +1 -1
  243. package/modules/permutiveRtdProvider.md +6 -5
  244. package/modules/pubmaticAnalyticsAdapter.js +61 -19
  245. package/modules/riseBidAdapter.js +11 -92
  246. package/modules/riseBidAdapter.md +1 -1
  247. package/modules/sharedIdSystem.js +9 -3
  248. package/modules/shinezBidAdapter.js +4 -80
  249. package/modules/shinezBidAdapter.md +2 -2
  250. package/modules/stnBidAdapter.js +7 -86
  251. package/modules/stnBidAdapter.md +1 -1
  252. package/modules/symitriDapRtdProvider.js +4 -4
  253. package/modules/symitriDapRtdProvider.md +2 -2
  254. package/modules/userId/eids.js +32 -5
  255. package/modules/wurflRtdProvider.js +7 -1
  256. package/modules/wurflRtdProvider.md +1 -1
  257. package/modules/yieldlabBidAdapter.js +2 -2
  258. package/modules/zeta_global_sspAnalyticsAdapter.js +5 -2
  259. package/package.json +2 -2
  260. package/src/adapters/bidderFactory.js +7 -6
  261. package/src/adloader.js +0 -1
  262. package/src/native.js +6 -3
  263. package/test/spec/creative/nativeRenderer_spec.js +45 -10
  264. package/test/spec/libraries/mspa/activityControls_spec.js +63 -9
  265. package/test/spec/modules/33acrossIdSystem_spec.js +288 -2
  266. package/test/spec/modules/acuityadsBidAdapter_spec.js +49 -0
  267. package/test/spec/modules/adagioRtdProvider_spec.js +119 -3
  268. package/test/spec/modules/adlooxAdServerVideo_spec.js +0 -1
  269. package/test/spec/modules/adlooxAnalyticsAdapter_spec.js +31 -0
  270. package/test/spec/modules/blueBidAdapter_spec.js +91 -0
  271. package/test/spec/modules/consumableBidAdapter_spec.js +42 -0
  272. package/test/spec/modules/contxtfulRtdProvider_spec.js +37 -2
  273. package/test/spec/modules/eplanningBidAdapter_spec.js +74 -1
  274. package/test/spec/modules/equativBidAdapter_spec.js +5 -6
  275. package/test/spec/modules/excoBidAdapter_spec.js +33 -18
  276. package/test/spec/modules/illuminBidAdapter_spec.js +33 -18
  277. package/test/spec/modules/improvedigitalBidAdapter_spec.js +3 -3
  278. package/test/spec/modules/ixBidAdapter_spec.js +68 -2
  279. package/test/spec/modules/kueezRtbBidAdapter_spec.js +33 -18
  280. package/test/spec/modules/liveIntentExternalIdSystem_spec.js +16 -6
  281. package/test/spec/modules/liveIntentIdMinimalSystem_spec.js +16 -6
  282. package/test/spec/modules/liveIntentIdSystem_spec.js +49 -6
  283. package/test/spec/modules/liveIntentRtdProvider_spec.js +116 -0
  284. package/test/spec/modules/minutemediaBidAdapter_spec.js +126 -6
  285. package/test/spec/modules/missenaBidAdapter_spec.js +12 -3
  286. package/test/spec/modules/omsBidAdapter_spec.js +78 -0
  287. package/test/spec/modules/openwebBidAdapter_spec.js +126 -6
  288. package/test/spec/modules/openxBidAdapter_spec.js +236 -8
  289. package/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +29 -36
  290. package/test/spec/modules/riseBidAdapter_spec.js +126 -6
  291. package/test/spec/modules/sharedIdSystem_spec.js +25 -1
  292. package/test/spec/modules/shinezBidAdapter_spec.js +133 -9
  293. package/test/spec/modules/shinezRtbBidAdapter_spec.js +33 -18
  294. package/test/spec/modules/stnBidAdapter_spec.js +126 -5
  295. package/test/spec/modules/symitriDapRtdProvider_spec.js +36 -0
  296. package/test/spec/modules/tagorasBidAdapter_spec.js +33 -18
  297. package/test/spec/modules/twistDigitalBidAdapter_spec.js +34 -18
  298. package/test/spec/modules/userId_spec.js +103 -2
  299. package/test/spec/modules/vidazooBidAdapter_spec.js +34 -18
  300. package/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +81 -0
  301. package/test/spec/modules/wurflRtdProvider_spec.js +8 -3
  302. package/test/spec/modules/yieldlabBidAdapter_spec.js +5 -5
  303. package/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +3 -2
  304. package/test/spec/native_spec.js +4 -2
  305. package/test/spec/unit/core/bidderFactory_spec.js +120 -43
@@ -0,0 +1,28 @@
1
+ # Overview
2
+
3
+ Module Name: Blue Bidder Adapter
4
+ Module Type: Bidder Adapter
5
+ Maintainer: celsooliveira@getblue.io
6
+
7
+ # Description
8
+
9
+ Module that connects to Blue's demand sources.
10
+
11
+ # Test Parameters
12
+ ```
13
+ var adUnits = [
14
+ {
15
+ code: 'banner-ad-div',
16
+ sizes: [[300, 250], [728, 90]],
17
+ bids: [
18
+ {
19
+ bidder: 'blue',
20
+ params: {
21
+ publisherId: "xpto",
22
+ placementId: "xpto",
23
+ }
24
+ }
25
+ ]
26
+ }
27
+ ];
28
+ ```
@@ -5,10 +5,12 @@ import { BANNER } from '../src/mediaTypes.js';
5
5
  const BIDDER_CODE = 'conceptx';
6
6
  const ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb';
7
7
  // const LOG_PREFIX = 'ConceptX: ';
8
+ const GVLID = 1340;
8
9
 
9
10
  export const spec = {
10
11
  code: BIDDER_CODE,
11
12
  supportedMediaTypes: [BANNER],
13
+ gvlid: GVLID,
12
14
  isBidRequestValid: function (bid) {
13
15
  return !!(bid.bidId && bid.params.site && bid.params.adunit);
14
16
  },
@@ -33,7 +33,8 @@ export const spec = {
33
33
  /**
34
34
  * Make a server request from the list of BidRequests.
35
35
  *
36
- * @param {validBidRequests[]} - an array of bids
36
+ * @param {validBidRequests[]} validBidRequests An array of bids
37
+ * @param {Object} bidderRequest The bidder's request info.
37
38
  * @return ServerRequest Info describing the request to the server.
38
39
  */
39
40
 
@@ -300,6 +301,7 @@ function retrieveAd(decision, unitId, unitName) {
300
301
  function handleEids(data, validBidRequests) {
301
302
  let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids');
302
303
  if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) {
304
+ bidUserIdAsEids = bidUserIdAsEids.filter(e => typeof e === 'object');
303
305
  deepSetValue(data, 'user.eids', bidUserIdAsEids);
304
306
  } else {
305
307
  deepSetValue(data, 'user.eids', undefined);
@@ -8,7 +8,7 @@ import {
8
8
  interpretResponse,
9
9
  getUserSyncs as getUserSyncsLib,
10
10
  } from '../libraries/teqblazeUtils/bidderUtils.js';
11
- import {ortbConverter} from '../libraries/ortbConverter/converter.js';
11
+ import { ortbConverter } from '../libraries/ortbConverter/converter.js';
12
12
 
13
13
  // Constants
14
14
  const BIDDER_CODE = 'contxtful';
@@ -16,6 +16,7 @@ const BIDDER_ENDPOINT = 'prebid.receptivity.io';
16
16
  const MONITORING_ENDPOINT = 'monitoring.receptivity.io';
17
17
  const DEFAULT_NET_REVENUE = true;
18
18
  const DEFAULT_TTL = 300;
19
+ const DEFAULT_SAMPLING_RATE = 1.0;
19
20
  const PREBID_VERSION = '$prebid.version$';
20
21
 
21
22
  // ORTB conversion
@@ -74,7 +75,7 @@ const extractParameters = (config) => {
74
75
 
75
76
  // Construct the Payload towards the Bidding endpoint
76
77
  const buildRequests = (validBidRequests = [], bidderRequest = {}) => {
77
- const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests});
78
+ const ortb2 = converter.toORTB({ bidderRequest: bidderRequest, bidRequests: validBidRequests });
78
79
 
79
80
  const bidRequests = [];
80
81
  _each(validBidRequests, bidRequest => {
@@ -87,14 +88,14 @@ const buildRequests = (validBidRequests = [], bidderRequest = {}) => {
87
88
  });
88
89
  const config = pbjsConfig.getConfig();
89
90
  config.pbjsVersion = PREBID_VERSION;
90
- const {version, customer} = extractParameters(config)
91
+ const { version, customer } = extractParameters(config)
91
92
  const adapterUrl = buildUrl({
92
93
  protocol: 'https',
93
94
  host: BIDDER_ENDPOINT,
94
95
  pathname: `/${version}/prebid/${customer}/bid`,
95
96
  });
96
97
 
97
- // https://docs.prebid.org/dev-docs/bidder-adaptor.html
98
+ // See https://docs.prebid.org/dev-docs/bidder-adaptor.html
98
99
  let req = {
99
100
  url: adapterUrl,
100
101
  method: 'POST',
@@ -147,7 +148,18 @@ const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gpp
147
148
  // Retrieve the sampling rate for events
148
149
  const getSamplingRate = (bidderConfig, eventType) => {
149
150
  const entry = Object.entries(bidderConfig?.contxtful?.sampling ?? {}).find(([key, value]) => key.toLowerCase() === eventType.toLowerCase());
150
- return entry ? entry[1] : 0.001;
151
+ return entry ? entry[1] : DEFAULT_SAMPLING_RATE;
152
+ };
153
+
154
+ const logBidderError = ({ error, bidderRequest }) => {
155
+ if (error) {
156
+ let jsonReason = {
157
+ message: error.reason?.message,
158
+ stack: error.reason?.stack,
159
+ };
160
+ error.reason = jsonReason;
161
+ }
162
+ logEvent('onBidderError', { error, bidderRequest });
151
163
  };
152
164
 
153
165
  // Handles the logging of events
@@ -158,13 +170,13 @@ const logEvent = (eventType, data) => {
158
170
 
159
171
  // Get Config
160
172
  const bidderConfig = pbjsConfig.getConfig();
161
- const {version, customer} = extractParameters(bidderConfig);
173
+ const { version, customer } = extractParameters(bidderConfig);
162
174
 
163
175
  // Sampled monitoring
164
176
  if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) {
165
177
  const randomNumber = Math.random();
166
178
  const samplingRate = getSamplingRate(bidderConfig, eventType);
167
- if (randomNumber >= samplingRate) {
179
+ if (!(randomNumber <= samplingRate)) {
168
180
  return; // Don't sample
169
181
  }
170
182
  } else if (!['onTimeout', 'onBidderError', 'onBidWon'].includes(eventType)) {
@@ -205,12 +217,12 @@ export const spec = {
205
217
  buildRequests,
206
218
  interpretResponse,
207
219
  getUserSyncs,
208
- onBidWon: function(bid) { logEvent('onBidWon', bid); },
209
- onBidBillable: function(bid) { logEvent('onBidBillable', bid); },
210
- onAdRenderSucceeded: function(bid) { logEvent('onAdRenderSucceeded', bid); },
211
- onSetTargeting: function(bid) { },
212
- onTimeout: function(timeoutData) { logEvent('onTimeout', timeoutData); },
213
- onBidderError: function({ error, bidderRequest }) { logEvent('onBidderError', { error, bidderRequest }); },
220
+ onBidWon: function (bid) { logEvent('onBidWon', bid); },
221
+ onBidBillable: function (bid) { logEvent('onBidBillable', bid); },
222
+ onAdRenderSucceeded: function (bid) { logEvent('onAdRenderSucceeded', bid); },
223
+ onSetTargeting: function (bid) { },
224
+ onTimeout: function (timeoutData) { logEvent('onTimeout', timeoutData); },
225
+ onBidderError: logBidderError,
214
226
  };
215
227
 
216
228
  registerBidder(spec);
@@ -15,6 +15,7 @@ import {
15
15
  isEmpty,
16
16
  buildUrl,
17
17
  isArray,
18
+ generateUUID,
18
19
  } from '../src/utils.js';
19
20
  import { loadExternalScript } from '../src/adloader.js';
20
21
  import { getStorageManager } from '../src/storageManager.js';
@@ -26,13 +27,17 @@ const MODULE = `${MODULE_NAME}RtdProvider`;
26
27
  const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io';
27
28
  const CONTXTFUL_DEFER_DEFAULT = 0;
28
29
 
30
+ let _sm;
31
+ function sm() {
32
+ return _sm ??= generateUUID();
33
+ }
34
+
29
35
  const storageManager = getStorageManager({
30
36
  moduleType: MODULE_TYPE_RTD,
31
37
  moduleName: MODULE_NAME,
32
38
  });
33
39
 
34
40
  let rxApi = null;
35
- let isFirstBidRequestCall = true;
36
41
 
37
42
  /**
38
43
  * Return current receptivity value for the requester.
@@ -150,7 +155,7 @@ function initCustomer(config) {
150
155
 
151
156
  addConnectorEventListener(customer, config);
152
157
 
153
- const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME);
158
+ const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME, undefined, undefined, { 'data-sm': sm() });
154
159
  // Optionally defer the loading of the script
155
160
  if (Number.isFinite(defer) && defer > 0) {
156
161
  setTimeout(loadScript, defer);
@@ -228,9 +233,6 @@ function getTargetingData(adUnits, config, _userConsent) {
228
233
  */
229
234
  function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) {
230
235
  function onReturn() {
231
- if (isFirstBidRequestCall) {
232
- isFirstBidRequestCall = false;
233
- }
234
236
  onDone();
235
237
  }
236
238
 
@@ -245,16 +247,10 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) {
245
247
  let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`));
246
248
 
247
249
  let sources = [fromStorage, fromApi];
248
- if (isFirstBidRequestCall) {
249
- sources.reverse();
250
- }
251
250
 
252
251
  let rxBatch = Object.assign(...sources);
253
252
 
254
- let singlePointEvents;
255
- if (isEmpty(rxBatch)) {
256
- singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() }));
257
- }
253
+ let singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() }));
258
254
 
259
255
  bidders
260
256
  .forEach(bidderCode => {
@@ -266,6 +262,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) {
266
262
  ext: {
267
263
  rx: rxBatch[bidderCode],
268
264
  events: singlePointEvents,
265
+ sm: sm(),
269
266
  params: {
270
267
  ev: config.params?.version,
271
268
  ci: config.params?.customer,
@@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
4
4
  import {getStorageManager} from '../src/storageManager.js';
5
5
  import {BANNER, VIDEO} from '../src/mediaTypes.js';
6
6
  import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
7
+ import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js';
7
8
 
8
9
  const BIDDER_CODE = 'eplanning';
9
10
  export const storage = getStorageManager({bidderCode: BIDDER_CODE});
@@ -39,6 +40,7 @@ export const spec = {
39
40
  const method = 'GET';
40
41
  const dfpClientId = '1';
41
42
  const sec = 'ROS';
43
+ const schain = bidRequests[0].schain;
42
44
  let url;
43
45
  let params;
44
46
  const urlConfig = getUrlConfig(bidRequests);
@@ -70,7 +72,9 @@ export const spec = {
70
72
  if (pcrs) {
71
73
  params.crs = pcrs;
72
74
  }
73
-
75
+ if (schain && schain.nodes.length <= 2) {
76
+ params.sch = serializeSupplyChain(schain, ['asi', 'sid', 'hp', 'rid', 'name', 'domain']);
77
+ }
74
78
  if (referrerUrl) {
75
79
  params.fr = cutUrl(referrerUrl);
76
80
  }
@@ -143,24 +143,19 @@ export const converter = ortbConverter({
143
143
  let env = ['ortb2.site.publisher', 'ortb2.app.publisher', 'ortb2.dooh.publisher'].find(propPath => deepAccess(bid, propPath)) || 'ortb2.site.publisher';
144
144
  deepSetValue(req, env.replace('ortb2.', '') + '.id', deepAccess(bid, env + '.id') || bid.params.networkId);
145
145
 
146
- if (deepAccess(bid, 'mediaTypes.video')) {
147
- ['mimes', 'placement'].forEach(prop => {
148
- if (!bid.mediaTypes.video[prop]) {
149
- logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request`, bid);
150
- }
151
- });
152
- }
153
-
154
- // "assets" is not included as a property to check here because the
155
- // ortbConverter library checks for it already and will skip processing
156
- // the request if it is missing
157
- if (deepAccess(bid, 'mediaTypes.native')) {
158
- ['privacy', 'plcmttype', 'eventtrackers'].forEach(prop => {
159
- if (!bid.mediaTypes.native.ortb[prop]) {
160
- logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request. Request will proceed, but the use of ${prop} for native requests is strongly encouraged.`, bid);
161
- }
162
- });
163
- }
146
+ [
147
+ { path: 'mediaTypes.video', props: ['mimes', 'placement'] },
148
+ { path: 'ortb2Imp.audio', props: ['mimes'] },
149
+ { path: 'mediaTypes.native.ortb', props: ['privacy', 'plcmttype', 'eventtrackers'] },
150
+ ].forEach(({ path, props }) => {
151
+ if (deepAccess(bid, path)) {
152
+ props.forEach(prop => {
153
+ if (!deepAccess(bid, `${path}.${prop}`)) {
154
+ logWarn(`${LOG_PREFIX} Property "${path}.${prop}" is missing from request. Request will proceed, but the use of "${prop}" is strongly encouraged.`, bid);
155
+ }
156
+ });
157
+ }
158
+ });
164
159
 
165
160
  const pid = storage.getCookie(PID_COOKIE_NAME);
166
161
  if (pid) {
@@ -84,8 +84,8 @@ export const spec = {
84
84
  const subdomain = getSubdomain();
85
85
  const endpointURL = ESCALAX_URL
86
86
  .replace(ESCALAX_SUBDOMAIN_MACRO, subdomain || ESCALAX_DEFAULT_SUBDOMAIN)
87
- .replace(ESCALAX_ACCOUNT_ID_MACRO, sourceId)
88
- .replace(ESCALAX_SOURCE_ID_MACRO, accountId);
87
+ .replace(ESCALAX_SOURCE_ID_MACRO, sourceId)
88
+ .replace(ESCALAX_ACCOUNT_ID_MACRO, accountId);
89
89
  const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest });
90
90
  return {
91
91
  method: 'POST',
@@ -232,7 +232,10 @@ function createRequest(bidRequests, bidderRequest, mediaType) {
232
232
  method: 'POST',
233
233
  url: getBidRequestUrlByRegion(),
234
234
  data: data,
235
- options: {contentType: 'application/json;charset=UTF-8', withCredentials: false}
235
+ options: {
236
+ withCredentials: true,
237
+ contentType: 'application/json;charset=UTF-8',
238
+ }
236
239
  }
237
240
  }
238
241
 
@@ -6,14 +6,6 @@ import {Renderer} from '../src/Renderer.js';
6
6
  import {hasPurpose1Consent} from '../src/utils/gdpr.js';
7
7
  import {ortbConverter} from '../libraries/ortbConverter/converter.js';
8
8
  import {convertCurrency} from '../libraries/currencyUtils/currency.js';
9
- /**
10
- * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception
11
- * ImproveDigital only imports after winning a bid and only if the creative cannot reach top
12
- * Also see https://github.com/prebid/Prebid.js/issues/11656
13
- */
14
- // eslint-disable-next-line no-restricted-imports
15
- import {loadExternalScript} from '../src/adloader.js';
16
- import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js';
17
9
 
18
10
  /**
19
11
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -225,7 +217,6 @@ export const CONVERTER = ortbConverter({
225
217
  renderer: ID_OUTSTREAM.createRenderer(bidRequest)
226
218
  })
227
219
  }
228
- ID_RAZR.forwardBid({bidRequest, bid: bidResponse});
229
220
  return bidResponse;
230
221
  },
231
222
  overrides: {
@@ -367,61 +358,3 @@ const ID_OUTSTREAM = {
367
358
  bid.renderer.handleVideoEvent({ id, eventName });
368
359
  },
369
360
  };
370
-
371
- const ID_RAZR = {
372
- RENDERER_URL: 'https://cdn.360yield.com/razr/tag.js',
373
-
374
- forwardBid({bidRequest, bid}) {
375
- if (bid.mediaType !== BANNER) {
376
- return;
377
- }
378
-
379
- const cfg = {
380
- prebid: {
381
- bidRequest,
382
- bid
383
- }
384
- };
385
-
386
- const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>');
387
- const s = `<script>window.__razr_config = ${cfgStr};</script>`;
388
- // prepend RAZR config to ad markup:
389
- bid.ad = s + bid.ad;
390
-
391
- this.installListener();
392
- },
393
-
394
- installListener() {
395
- if (this._listenerInstalled) {
396
- return;
397
- }
398
-
399
- window.addEventListener('message', function(e) {
400
- const data = e.data?.razr?.load;
401
- if (!data) {
402
- return;
403
- }
404
-
405
- if (e.source) {
406
- data.source = e.source;
407
- if (data.id) {
408
- e.source.postMessage({
409
- razr: {
410
- id: data.id
411
- }
412
- }, '*');
413
- }
414
- }
415
-
416
- const ns = window.razr = window.razr || {};
417
- ns.q = ns.q || [];
418
- ns.q.push(data);
419
-
420
- if (!ns.loaded) {
421
- loadExternalScript(ID_RAZR.RENDERER_URL, MODULE_TYPE_BIDDER, BIDDER_CODE);
422
- }
423
- });
424
-
425
- this._listenerInstalled = true;
426
- }
427
- };
@@ -304,7 +304,7 @@ function constructFullUrl(data) {
304
304
  '&jsver=' + VERSION +
305
305
  '&source=pbjs' +
306
306
  '&payload=' + JSON.stringify(report) +
307
- '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints +
307
+ '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) +
308
308
  (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : '');
309
309
 
310
310
  url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName);
@@ -496,6 +496,11 @@ function parseBid(rawBid, currency, bidRequest) {
496
496
  if (rawBid.ext?.dsa) {
497
497
  bid.meta.dsa = rawBid.ext.dsa
498
498
  }
499
+
500
+ if (rawBid.ext?.ibv) {
501
+ bid.ext = bid.ext || {}
502
+ bid.ext.ibv = rawBid.ext.ibv
503
+ }
499
504
  return bid;
500
505
  }
501
506
 
@@ -751,8 +756,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
751
756
  method: 'POST',
752
757
  url: exchangeUrl,
753
758
  data: deepClone(r),
754
- option: {
759
+ options: {
755
760
  contentType: 'text/plain',
761
+ withCredentials: true
756
762
  },
757
763
  validBidRequests
758
764
  });
@@ -50,6 +50,8 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba
50
50
  VIDEO_MIME_TYPE.AAC,
51
51
  VIDEO_MIME_TYPE.HLS
52
52
  ];
53
+ let height = null;
54
+ let width = null;
53
55
 
54
56
  function init() {
55
57
  if (!jwplayer) {
@@ -92,6 +94,20 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba
92
94
  const adConfig = config.advertising || {};
93
95
  supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES);
94
96
 
97
+ if (height === null) {
98
+ height = utils.getPlayerHeight(player, config);
99
+ }
100
+
101
+ if (width === null) {
102
+ width = utils.getPlayerWidth(player, config);
103
+ }
104
+
105
+ if (config.aspectratio && !height && !width) {
106
+ const size = utils.getPlayerSizeFromAspectRatio(player, config);
107
+ height = size.height;
108
+ width = size.width;
109
+ }
110
+
95
111
  const video = {
96
112
  mimes: supportedMediaTypes,
97
113
  protocols: [
@@ -102,8 +118,8 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba
102
118
  PROTOCOLS.VAST_3_0_WRAPPER,
103
119
  PROTOCOLS.VAST_4_0_WRAPPER
104
120
  ],
105
- h: player.getHeight(), // TODO does player call need optimization ?
106
- w: player.getWidth(), // TODO does player call need optimization ?
121
+ h: height,
122
+ w: width,
107
123
  startdelay: utils.getStartDelay(),
108
124
  placement: utils.getPlacement(adConfig, player),
109
125
  // linearity is omitted because both forms are supported.
@@ -414,10 +430,14 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba
414
430
  break;
415
431
 
416
432
  case PLAYER_RESIZE:
417
- getEventPayload = e => ({
418
- height: e.height,
419
- width: e.width,
420
- });
433
+ getEventPayload = e => {
434
+ height = e.height;
435
+ width = e.width;
436
+ return {
437
+ height,
438
+ width
439
+ };
440
+ };
421
441
  break;
422
442
 
423
443
  case VIEWABLE:
@@ -585,6 +605,79 @@ export const utils = {
585
605
  return jwConfig;
586
606
  },
587
607
 
608
+ getPlayerHeight: function(player, config) {
609
+ let height;
610
+
611
+ if (player.getHeight) {
612
+ height = player.getHeight();
613
+ }
614
+
615
+ // Height is undefined when player has not yet rendered
616
+ if (height !== undefined) {
617
+ return height;
618
+ }
619
+
620
+ return config.height;
621
+ },
622
+
623
+ getPlayerWidth: function(player, config) {
624
+ let width;
625
+
626
+ if (player.getWidth) {
627
+ width = player.getWidth();
628
+ }
629
+
630
+ // Width is undefined when player has not yet rendered
631
+ if (width !== undefined) {
632
+ return width;
633
+ }
634
+
635
+ // Width can be a string when aspectratio is set
636
+ if (typeof config.width === 'number') {
637
+ return config.width;
638
+ }
639
+ },
640
+
641
+ getPlayerSizeFromAspectRatio: function(player, config) {
642
+ const aspectRatio = config.aspectratio;
643
+ let percentageWidth = config.width;
644
+
645
+ if (typeof aspectRatio !== 'string' || typeof percentageWidth !== 'string') {
646
+ return {};
647
+ }
648
+
649
+ const ratios = aspectRatio.split(':');
650
+
651
+ if (ratios.length !== 2) {
652
+ return {};
653
+ }
654
+
655
+ const containerElement = player.getContainer && player.getContainer();
656
+ if (!containerElement) {
657
+ return {};
658
+ }
659
+
660
+ const containerWidth = containerElement.clientWidth;
661
+ const containerHeight = containerElement.clientHeight;
662
+
663
+ const xRatio = parseInt(ratios[0], 10);
664
+ const yRatio = parseInt(ratios[1], 10);
665
+
666
+ if (isNaN(xRatio) || isNaN(yRatio)) {
667
+ return {};
668
+ }
669
+
670
+ const numericWidthPercentage = parseInt(percentageWidth, 10);
671
+
672
+ const desiredWidth = containerWidth * numericWidthPercentage / 100;
673
+ const desiredHeight = Math.min(desiredWidth * yRatio / xRatio, containerHeight);
674
+
675
+ return {
676
+ height: desiredHeight,
677
+ width: desiredWidth
678
+ };
679
+ },
680
+
588
681
  getJwEvent: function(eventName) {
589
682
  switch (eventName) {
590
683
  case SETUP_COMPLETE:
@@ -0,0 +1,52 @@
1
+ /**
2
+ * This module adds the LiveIntent provider to the Real Time Data module (rtdModule).
3
+ */
4
+ import { submodule } from '../src/hook.js';
5
+ import {deepAccess, deepSetValue} from '../src/utils.js'
6
+
7
+ /**
8
+ * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
9
+ * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig
10
+ * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData
11
+ */
12
+
13
+ const SUBMODULE_NAME = 'liveintent';
14
+ const GVLID = 148;
15
+
16
+ /**
17
+ * Init
18
+ * @param {Object} config Module configuration
19
+ * @param {UserConsentData} userConsent User consent
20
+ * @returns true
21
+ */
22
+ const init = (config, userConsent) => {
23
+ return true;
24
+ }
25
+
26
+ /**
27
+ * onBidRequest is called for each bidder during an auction and contains the bids for that bidder.
28
+ *
29
+ * @param {Object} bidRequest
30
+ * @param {SubmoduleConfig} config
31
+ * @param {UserConsentData} userConsent
32
+ */
33
+
34
+ function onBidRequest(bidRequest, config, userConsent) {
35
+ bidRequest.bids.forEach(bid => {
36
+ const providedSegmentsFromUserId = deepAccess(bid, 'userId.lipb.segments', [])
37
+ if (providedSegmentsFromUserId.length > 0) {
38
+ const providedSegments = { name: 'liveintent.com', segment: providedSegmentsFromUserId.map(id => ({ id })) }
39
+ const existingData = deepAccess(bid, 'ortb2.user.data', [])
40
+ deepSetValue(bid, 'ortb2.user.data', existingData.concat(providedSegments))
41
+ }
42
+ })
43
+ }
44
+
45
+ export const liveIntentRtdSubmodule = {
46
+ name: SUBMODULE_NAME,
47
+ gvlid: GVLID,
48
+ init: init,
49
+ onBidRequestEvent: onBidRequest
50
+ };
51
+
52
+ submodule('realTimeData', liveIntentRtdSubmodule);
@@ -0,0 +1,45 @@
1
+ # Overview
2
+
3
+ Module Name: LiveIntent Provider
4
+ Module Type: Rtd Provider
5
+ Maintainer: product@liveIntent.com
6
+
7
+ # Description
8
+
9
+ This module extracts segments from `bidRequest.userId.lipb.segments` enriched by the userID module and
10
+ injects them in `ortb2.user.data` array entry.
11
+
12
+ Please visit [LiveIntent](https://www.liveIntent.com/) for more information.
13
+
14
+ # Testing
15
+
16
+ To run the example and test the Rtd provider:
17
+
18
+ ```sh
19
+ gulp serve --modules=appnexusBidAdapter,rtdModule,liveIntentRtdProvider,userId,liveIntentIdSystem
20
+ ```
21
+
22
+ Open chrome with this URL:
23
+ `http://localhost:9999/integrationExamples/gpt/liveIntentRtdProviderExample.html`
24
+
25
+ To run the unit test:
26
+ ```sh
27
+ gulp test --file "test/spec/modules/liveIntentRtdProvider_spec.js"
28
+ ```
29
+
30
+ # Integration
31
+
32
+ ```bash
33
+ gulp build --modules=userId,liveIntentIdSystem,rtdModule,liveIntentRtdProvider
34
+ ```
35
+
36
+ ```javascript
37
+ pbjs.setConfig({
38
+ realTimeData: {
39
+ dataProviders:[{
40
+ name: 'liveintent',
41
+ waitForIt: true
42
+ }]
43
+ }
44
+ });
45
+ ```