• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / #295

15 May 2025 11:51AM UTC coverage: 90.118% (+0.005%) from 90.113%
#295

push

travis-ci

prebidjs-release
Prebid 9.43.0 release

42777 of 53837 branches covered (79.46%)

63509 of 70473 relevant lines covered (90.12%)

179.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.51
/modules/sovrnBidAdapter.js
1
import {
1✔
2
  _each,
3
  isArray,
4
  getUniqueIdentifierStr,
5
  deepSetValue,
6
  logError,
7
  deepAccess,
8
  isInteger,
9
  logWarn,
10
  getBidIdParameter,
11
  isEmptyStr,
12
  mergeDeep
13
} from '../src/utils.js';
14
import { registerBidder } from '../src/adapters/bidderFactory.js'
15
import {
16
  BANNER,
17
  VIDEO
18
} from '../src/mediaTypes.js'
19
import { COMMON_ORTB_VIDEO_PARAMS } from '../libraries/deepintentUtils/index.js';
20

21
/**
22
 * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
23
 */
24

25
const ORTB_VIDEO_PARAMS = {
1✔
26
  ...COMMON_ORTB_VIDEO_PARAMS,
27
  'placement': (value) => isInteger(value) && value >= 1 && value <= 5,
×
28
  'plcmt': (value) => isInteger(value) && value >= 1 && value <= 4,
×
29
  'delivery': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 3),
×
30
  'pos': (value) => isInteger(value) && value >= 1 && value <= 7,
×
31
}
32

33
const REQUIRED_VIDEO_PARAMS = {
1✔
34
  mimes: ORTB_VIDEO_PARAMS.mimes,
35
  maxduration: ORTB_VIDEO_PARAMS.maxduration,
36
  protocols: ORTB_VIDEO_PARAMS.protocols
37
}
38

39
export const spec = {
1✔
40
  code: 'sovrn',
41
  supportedMediaTypes: [BANNER, VIDEO],
42
  gvlid: 13,
43

44
  /**
45
   * Check if the bid is a valid zone ID in either number or string form
46
   * @param {object} bid the Sovrn bid to validate
47
   * @return boolean for whether or not a bid is valid
48
   */
49
  isBidRequestValid: function (bid) {
50
    const video = bid?.mediaTypes?.video
48✔
51
    return !!(
48✔
52
      bid.params.tagid &&
189✔
53
      !isNaN(parseFloat(bid.params.tagid)) &&
54
      isFinite(bid.params.tagid) && (
55
        !video || (
56
          Object.keys(REQUIRED_VIDEO_PARAMS)
57
            .every(key => REQUIRED_VIDEO_PARAMS[key](video[key]))
4✔
58
        )
59
      )
60
    )
61
  },
62

63
  /**
64
   * Format the bid request object for our endpoint
65
   * @return object of parameters for Prebid AJAX request
66
   * @param bidReqs
67
   * @param bidderRequest
68
   */
69
  buildRequests: function(bidReqs, bidderRequest) {
70
    try {
78✔
71
      let sovrnImps = [];
78✔
72
      let iv;
73
      let schain;
74
      let eids;
75
      let criteoId;
76

77
      _each(bidReqs, function (bid) {
78✔
78
        if (!eids && bid.userIdAsEids) {
80✔
79
          eids = bid.userIdAsEids;
2✔
80
          eids.forEach(function (id) {
2✔
81
            if (id.uids && id.uids[0]) {
3!
82
              if (id.source === 'criteo.com') {
3✔
83
                criteoId = id.uids[0].id
1✔
84
              }
85
            }
86
          })
87
        }
88

89
        if (bid.schain) {
80✔
90
          schain = schain || bid.schain
1✔
91
        }
92
        iv = iv || getBidIdParameter('iv', bid.params)
80✔
93

94
        const imp = {
80✔
95
          adunitcode: bid.adUnitCode,
96
          id: bid.bidId,
97
          tagid: String(getBidIdParameter('tagid', bid.params)),
98
          bidfloor: _getBidFloors(bid)
99
        }
100

101
        if (deepAccess(bid, 'mediaTypes.banner')) {
80✔
102
          let bidSizes = deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes
46✔
103
          bidSizes = (isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]
46✔
104
          bidSizes = bidSizes.filter(size => isArray(size))
91✔
105
          const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)}))
91✔
106

107
          imp.banner = {
46✔
108
            format: processedSizes,
109
            w: 1,
110
            h: 1,
111
          };
112
        }
113
        if (deepAccess(bid, 'mediaTypes.video')) {
80✔
114
          imp.video = _buildVideoRequestObj(bid);
2✔
115
        }
116

117
        imp.ext = getBidIdParameter('ext', bid.ortb2Imp) || undefined
80✔
118

119
        const segmentsString = getBidIdParameter('segments', bid.params)
80✔
120
        if (segmentsString) {
80✔
121
          imp.ext = imp.ext || {}
2✔
122
          imp.ext.deals = segmentsString.split(',').map(deal => deal.trim())
4✔
123
        }
124

125
        const auctionEnvironment = bid?.ortb2Imp?.ext?.ae
80✔
126
        if (bidderRequest.paapi?.enabled && isInteger(auctionEnvironment)) {
80✔
127
          imp.ext = imp.ext || {}
1!
128
          imp.ext.ae = auctionEnvironment
1✔
129
        } else {
130
          if (imp.ext?.ae) {
79✔
131
            delete imp.ext.ae
2✔
132
          }
133
        }
134

135
        sovrnImps.push(imp)
80✔
136
      })
137

138
      const fpd = bidderRequest.ortb2 || {};
78✔
139

140
      const site = fpd.site || {}
78✔
141
      site.page = bidderRequest.refererInfo.page
78✔
142
      site.domain = bidderRequest.refererInfo.domain
78✔
143

144
      const tmax = deepAccess(bidderRequest, 'timeout');
78✔
145

146
      const sovrnBidReq = {
78✔
147
        id: getUniqueIdentifierStr(),
148
        imp: sovrnImps,
149
        site: site,
150
        user: fpd.user || {},
155✔
151
        tmax: tmax
152
      }
153

154
      if (schain) {
78✔
155
        sovrnBidReq.source = {
1✔
156
          ext: {
157
            schain
158
          }
159
        };
160
      }
161

162
      const tid = deepAccess(bidderRequest, 'ortb2.source.tid')
78✔
163
      if (tid) {
78✔
164
        deepSetValue(sovrnBidReq, 'source.tid', tid)
1✔
165
      }
166

167
      const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa');
78✔
168
      if (coppa) {
78✔
169
        deepSetValue(sovrnBidReq, 'regs.coppa', 1);
1✔
170
      }
171

172
      const bcat = deepAccess(bidderRequest, 'ortb2.bcat');
78✔
173
      if (bcat) {
78✔
174
        deepSetValue(sovrnBidReq, 'bcat', bcat);
1✔
175
      }
176

177
      if (bidderRequest.gdprConsent) {
78✔
178
        deepSetValue(sovrnBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies);
5✔
179
        deepSetValue(sovrnBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString)
5✔
180
      }
181
      if (bidderRequest.uspConsent) {
78✔
182
        deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent);
1✔
183
      }
184
      if (bidderRequest.gppConsent) {
78✔
185
        deepSetValue(sovrnBidReq, 'regs.gpp', bidderRequest.gppConsent.gppString);
2✔
186
        deepSetValue(sovrnBidReq, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections);
2✔
187
      }
188

189
      // if present, merge device object from ortb2 into `sovrnBidReq.device`
190
      if (bidderRequest?.ortb2?.device) {
78✔
191
        sovrnBidReq.device = sovrnBidReq.device || {};
1✔
192
        mergeDeep(sovrnBidReq.device, bidderRequest.ortb2.device);
1✔
193
      }
194

195
      if (eids) {
78✔
196
        deepSetValue(sovrnBidReq, 'user.ext.eids', eids)
2✔
197
        if (criteoId) {
2✔
198
          deepSetValue(sovrnBidReq, 'user.ext.prebid_criteoid', criteoId)
1✔
199
        }
200
      }
201

202
      let url = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`;
78✔
203
      if (iv) url += `&iv=${iv}`;
78✔
204

205
      return {
78✔
206
        method: 'POST',
207
        url: url,
208
        data: JSON.stringify(sovrnBidReq),
209
        options: {contentType: 'text/plain'}
210
      }
211
    } catch (e) {
212
      logError('Could not build bidrequest, error deatils:', e);
×
213
    }
214
  },
215

216
  /**
217
   * Format Sovrn responses as Prebid bid responses
218
   * @param {id, seatbid, ext} sovrnResponse A successful response from Sovrn.
219
   * @return An array of formatted bids (+ fledgeAuctionConfigs if available)
220
   */
221
  interpretResponse: function({ body: {id, seatbid, ext} }) {
222
    if (!id || !seatbid || !Array.isArray(seatbid)) return []
14!
223

224
    try {
14✔
225
      let bids = seatbid
14✔
226
        .filter(seat => seat)
14✔
227
        .map(seat => seat.bid.map(sovrnBid => {
14✔
228
          const bid = {
14✔
229
            requestId: sovrnBid.impid,
230
            cpm: parseFloat(sovrnBid.price),
231
            width: parseInt(sovrnBid.w),
232
            height: parseInt(sovrnBid.h),
233
            creativeId: sovrnBid.crid || sovrnBid.id,
16✔
234
            dealId: sovrnBid.dealid || null,
26✔
235
            currency: 'USD',
236
            netRevenue: true,
237
            mediaType: sovrnBid.nurl ? BANNER : VIDEO,
14✔
238
            ttl: sovrnBid.ext?.ttl || 90,
26✔
239
            meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] }
42!
240
          }
241

242
          if (sovrnBid.nurl) {
14✔
243
            bid.ad = decodeURIComponent(`${sovrnBid.adm}<img src="${sovrnBid.nurl}">`)
9✔
244
          } else {
245
            bid.vastXml = decodeURIComponent(sovrnBid.adm)
5✔
246
          }
247

248
          return bid
14✔
249
        }))
250
        .flat()
251

252
      let fledgeAuctionConfigs = null;
14✔
253
      if (isArray(ext?.igbid)) {
14✔
254
        const seller = ext.seller
1✔
255
        const decisionLogicUrl = ext.decisionLogicUrl
1✔
256
        const sellerTimeout = ext.sellerTimeout
1✔
257
        ext.igbid.filter(item => isValidIgBid(item)).forEach((igbid) => {
5✔
258
          const perBuyerSignals = {}
3✔
259
          igbid.igbuyer.filter(item => isValidIgBuyer(item)).forEach(buyerItem => {
5✔
260
            perBuyerSignals[buyerItem.igdomain] = buyerItem.buyerdata
4✔
261
          })
262
          const interestGroupBuyers = [...Object.keys(perBuyerSignals)]
3✔
263
          if (interestGroupBuyers.length) {
3✔
264
            fledgeAuctionConfigs = fledgeAuctionConfigs || {}
2✔
265
            fledgeAuctionConfigs[igbid.impid] = {
2✔
266
              seller,
267
              decisionLogicUrl,
268
              sellerTimeout,
269
              interestGroupBuyers: interestGroupBuyers,
270
              perBuyerSignals,
271
            }
272
          }
273
        })
274
      }
275
      if (fledgeAuctionConfigs) {
14✔
276
        fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
1✔
277
          return {
2✔
278
            bidId,
279
            config: Object.assign({
280
              auctionSignals: {}
281
            }, cfg)
282
          }
283
        })
284
        return {
1✔
285
          bids,
286
          paapi: fledgeAuctionConfigs,
287
        }
288
      }
289
      return bids
13✔
290
    } catch (e) {
291
      logError('Could not interpret bidresponse, error details:', e)
×
292
      return e
×
293
    }
294
  },
295

296
  getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) {
297
    try {
8✔
298
      const tracks = []
8✔
299
      if (serverResponses && serverResponses.length !== 0) {
8✔
300
        if (syncOptions.iframeEnabled) {
7✔
301
          const iidArr = serverResponses.filter(resp => deepAccess(resp, 'body.ext.iid'))
5✔
302
            .map(resp => resp.body.ext.iid);
5✔
303
          const params = [];
5✔
304
          if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') {
5✔
305
            params.push(['gdpr_consent', gdprConsent.consentString]);
2✔
306
          }
307
          if (uspConsent) {
5✔
308
            params.push(['us_privacy', uspConsent]);
2✔
309
          }
310
          if (gppConsent) {
5✔
311
            params.push(['gpp', gppConsent.gppString]);
2✔
312
            params.push(['gpp_sid', gppConsent.applicableSections])
2✔
313
          }
314

315
          if (iidArr[0]) {
5!
316
            params.push(['informer', iidArr[0]]);
5✔
317
            tracks.push({
5✔
318
              type: 'iframe',
319
              url: 'https://ce.lijit.com/beacon?' + params.map(p => p.join('=')).join('&')
13✔
320
            });
321
          }
322
        }
323

324
        if (syncOptions.pixelEnabled) {
7✔
325
          serverResponses.filter(resp => deepAccess(resp, 'body.ext.sync.pixels'))
2✔
326
            .reduce((acc, resp) => acc.concat(resp.body.ext.sync.pixels), [])
2✔
327
            .map(pixel => pixel.url)
4✔
328
            .forEach(url => tracks.push({ type: 'image', url }))
4✔
329
        }
330
      }
331
      return tracks
8✔
332
    } catch (e) {
333
      return []
×
334
    }
335
  },
336
}
337

338
function _buildVideoRequestObj(bid) {
339
  const videoObj = {}
2✔
340
  const bidSizes = deepAccess(bid, 'sizes')
2✔
341
  const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {})
2✔
342
  const videoBidderParams = deepAccess(bid, 'params.video', {})
2✔
343
  const computedParams = {}
2✔
344

345
  if (bidSizes) {
2✔
346
    const sizes = (Array.isArray(bidSizes[0])) ? bidSizes[0] : bidSizes
1!
347
    computedParams.w = sizes[0]
1✔
348
    computedParams.h = sizes[1]
1✔
349
  } else if (Array.isArray(videoAdUnitParams.playerSize)) {
1!
350
    const sizes = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize
1!
351
    computedParams.w = sizes[0]
1✔
352
    computedParams.h = sizes[1]
1✔
353
  }
354

355
  const videoParams = {
2✔
356
    ...computedParams,
357
    ...videoAdUnitParams,
358
    ...videoBidderParams
359
  };
360

361
  Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => {
2✔
362
    if (videoParams.hasOwnProperty(paramName)) {
48✔
363
      if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) {
14!
364
        videoObj[paramName] = videoParams[paramName]
14✔
365
      } else {
366
        logWarn(`The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`);
×
367
      }
368
    }
369
  })
370
  return videoObj
2✔
371
}
372

373
function _getBidFloors(bid) {
374
  const floorInfo = (bid.getFloor && typeof bid.getFloor === 'function') ? bid.getFloor({
80✔
375
    currency: 'USD',
376
    mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video',
8!
377
    size: '*'
378
  }) : {}
379
  const floorModuleValue = parseFloat(floorInfo?.floor)
80✔
380
  if (!isNaN(floorModuleValue)) {
80✔
381
    return floorModuleValue
1✔
382
  }
383
  const paramValue = parseFloat(getBidIdParameter('bidfloor', bid.params))
79✔
384
  return !isNaN(paramValue) ? paramValue : undefined
79✔
385
}
386

387
function isValidIgBid(igBid) {
388
  return !isEmptyStr(igBid.impid) && isArray(igBid.igbuyer) && igBid.igbuyer.length
5✔
389
}
390

391
function isValidIgBuyer(igBuyer) {
392
  return !isEmptyStr(igBuyer.igdomain)
5✔
393
}
394

395
registerBidder(spec)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc