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

prebid / Prebid.js / 16457245554

22 Jul 2025 10:52PM UTC coverage: 96.262% (-0.003%) from 96.265%
16457245554

push

github

ba9a62
web-flow
Linting: no-return-assign (#13608)

* Update eslint.config.js

* merge remote: remove no return assign lint rule (#13609)

* Yieldlab Bid Adapter: ensure netRevenue default

* Core: enable no-return-assign rule

* Update yieldlabBidAdapter.js

* lint fix spacing

* Update eslint.config.js

---------

Co-authored-by: Chris Huie <phoenixtechnerd@gmail.com>

39335 of 48340 branches covered (81.37%)

104 of 111 new or added lines in 44 files covered. (93.69%)

19 existing lines in 15 files now uncovered.

194344 of 201890 relevant lines covered (96.26%)

83.16 hits per line

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

90.73
/modules/yieldmoBidAdapter.js
1
import {
1✔
2
  deepAccess,
3
  deepSetValue,
4
  getWinDimensions,
5
  getWindowTop,
6
  isArray,
7
  isArrayOfNums,
8
  isBoolean,
9
  isEmpty,
10
  isInteger,
11
  isNumber,
12
  isStr,
13
  logError,
14
  parseQueryStringParameters,
15
  parseUrl
16
} from '../src/utils.js';
17
import {BANNER, VIDEO} from '../src/mediaTypes.js';
18
import {registerBidder} from '../src/adapters/bidderFactory.js';
19
import {Renderer} from '../src/Renderer.js';
20

21
/**
22
 * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
23
 * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
24
 * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest
25
 * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse
26
 * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest
27
 */
28

29
const BIDDER_CODE = 'yieldmo';
1✔
30
const GVLID = 173;
1✔
31
const CURRENCY = 'USD';
1✔
32
const TIME_TO_LIVE = 300;
1✔
33
const NET_REVENUE = true;
1✔
34
const PB_COOKIE_ASSIST_SYNC_ENDPOINT = `https://ads.yieldmo.com/pbcas`;
1✔
35
const BANNER_PATH = '/exchange/prebid';
1✔
36
const VIDEO_PATH = '/exchange/prebidvideo';
1✔
37
const STAGE_DOMAIN = 'https://ads-stg.yieldmo.com';
1✔
38
const PROD_DOMAIN = 'https://ads.yieldmo.com';
1✔
39
const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js';
1✔
40
const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'plcmt', 'skipafter', 'protocols', 'api',
1✔
41
  'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable'];
42
const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords'];
1✔
43
const LOCAL_WINDOW = getWindowTop();
1✔
44
const DEFAULT_PLAYBACK_METHOD = 2;
1✔
45
const DEFAULT_START_DELAY = 0;
1✔
46
const VAST_TIMEOUT = 15000;
1✔
47
const MAX_BANNER_REQUEST_URL_LENGTH = 8000;
1✔
48
const BANNER_REQUEST_PROPERTIES_TO_REDUCE = ['description', 'title', 'pr', 'page_url'];
1✔
49

50
export const spec = {
1✔
51
  code: BIDDER_CODE,
52
  supportedMediaTypes: [BANNER, VIDEO],
53
  gvlid: GVLID,
54
  /**
55
   * Determines whether or not the given bid request is valid.
56
   * @param {object} bid bid to validate
57
   * @return {boolean} true if valid, otherwise false
58
   */
59
  isBidRequestValid: function (bid) {
60
    return !!(bid && bid.adUnitCode && bid.bidId && (hasBannerMediaType(bid) || hasVideoMediaType(bid)) &&
15✔
61
      validateVideoParams(bid));
62
  },
63

64
  /**
65
   * Make a server request from the list of BidRequests.
66
   *
67
   * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
68
   * @param {BidderRequest} bidderRequest bidder request object.
69
   * @return ServerRequest Info describing the request to the server.
70
   */
71
  buildRequests: function (bidRequests, bidderRequest) {
72
    const stage = isStage(bidderRequest);
57✔
73
    const bannerUrl = getAdserverUrl(BANNER_PATH, stage);
57✔
74
    const videoUrl = getAdserverUrl(VIDEO_PATH, stage);
57✔
75
    const bannerBidRequests = bidRequests.filter(request => hasBannerMediaType(request));
63✔
76
    const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request));
63✔
77
    const serverRequests = [];
57✔
78
    const eids = getEids(bidRequests[0]) || [];
57✔
79
    const topicsData = getTopics(bidderRequest);
57✔
80
    const cdep = getCdep(bidderRequest);
57✔
81
    if (bannerBidRequests.length > 0) {
57✔
82
      const serverRequest = {
29✔
83
        pbav: '$prebid.version$',
84
        p: [],
85
        // TODO: is 'page' the right value here?
86
        page_url: bidderRequest.refererInfo.page,
87
        bust: new Date().getTime().toString(),
88
        dnt: getDNT(),
89
        description: getPageDescription(),
90
        tmax: bidderRequest.timeout || 400,
57✔
91
        userConsent: JSON.stringify({
92
          // case of undefined, stringify will remove param
93
          gdprApplies:
94
            deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '',
57✔
95
          cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '',
57✔
96
          gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || '',
57✔
97
          gpp_sid:
98
            deepAccess(bidderRequest, 'gppConsent.applicableSections') || []}),
57✔
99
        us_privacy: deepAccess(bidderRequest, 'uspConsent') || '',
57✔
100
      };
101
      if (topicsData) {
29✔
102
        serverRequest.topics = JSON.stringify(topicsData);
1✔
103
      }
104
      const gpc = getGPCSignal(bidderRequest);
29✔
105
      if (gpc) {
29✔
106
        serverRequest.gpc = gpc;
1✔
107
      }
108
      if (cdep) {
29✔
109
        serverRequest.cdep = cdep;
1✔
110
      }
111

112
      if (canAccessTopWindow()) {
29✔
113
        serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || '';
29✔
114
        serverRequest.scrd = LOCAL_WINDOW.devicePixelRatio || 0;
29!
115
        serverRequest.title = LOCAL_WINDOW.document.title || '';
29!
116
        serverRequest.w = getWinDimensions().innerWidth;
29✔
117
        serverRequest.h = getWinDimensions().innerHeight;
29✔
118
      }
119

120
      const mtp = window.navigator.maxTouchPoints;
29✔
121
      if (mtp) {
29!
122
        serverRequest.mtp = mtp;
×
123
      }
124

125
      bannerBidRequests.forEach(request => {
29✔
126
        serverRequest.p.push(addPlacement(request));
33✔
127
        const pubcid = getId(request, 'pubcid');
33✔
128
        if (pubcid) {
33✔
129
          serverRequest.pubcid = pubcid;
1✔
130
        } else if (request.crumbs && request.crumbs.pubcid) {
32✔
131
          serverRequest.pubcid = request.crumbs.pubcid;
29✔
132
        }
133
        const tdid = getId(request, 'tdid');
33✔
134
        if (tdid) {
33✔
135
          serverRequest.tdid = tdid;
31✔
136
        }
137
        const criteoId = getId(request, 'criteoId');
33✔
138
        if (criteoId) {
33✔
139
          serverRequest.cri_prebid = criteoId;
1✔
140
        }
141
        const schain = deepAccess(request, 'ortb2.source.ext.schain');
33✔
142
        if (schain) {
33✔
143
          serverRequest.schain = JSON.stringify(schain);
1✔
144
        }
145
        if (deepAccess(request, 'params.lr_env')) {
33✔
146
          serverRequest.ats_envelope = request.params.lr_env;
1✔
147
        }
148
      });
149
      serverRequest.p = '[' + serverRequest.p.toString() + ']';
29✔
150

151
      if (eids.length) {
29✔
152
        serverRequest.eids = JSON.stringify(eids);
1✔
153
      };
154
      // check if url exceeded max length
155
      const fullUrl = `${bannerUrl}?${parseQueryStringParameters(serverRequest)}`;
29✔
156
      let extraCharacters = fullUrl.length - MAX_BANNER_REQUEST_URL_LENGTH;
29✔
157
      if (extraCharacters > 0) {
29✔
158
        for (let i = 0; i < BANNER_REQUEST_PROPERTIES_TO_REDUCE.length; i++) {
2✔
159
          extraCharacters = shortcutProperty(extraCharacters, serverRequest, BANNER_REQUEST_PROPERTIES_TO_REDUCE[i]);
4✔
160

161
          if (extraCharacters <= 0) {
4✔
162
            break;
2✔
163
          }
164
        }
165
      }
166

167
      serverRequests.push({
29✔
168
        method: 'GET',
169
        url: bannerUrl,
170
        data: serverRequest
171
      });
172
    }
173

174
    if (videoBidRequests.length > 0) {
57✔
175
      const serverRequest = openRtbRequest(videoBidRequests, bidderRequest);
28✔
176
      if (topicsData) {
28✔
177
        serverRequest.topics = topicsData;
1✔
178
      }
179
      if (eids.length) {
28✔
180
        deepSetValue(serverRequest, 'user.ext.eids', eids);
2✔
181
      };
182
      serverRequests.push({
28✔
183
        method: 'POST',
184
        url: videoUrl,
185
        data: serverRequest
186
      });
187
    }
188
    return serverRequests;
57✔
189
  },
190

191
  /**
192
   * Makes Yieldmo Ad Server response compatible to Prebid specs
193
   * @param {ServerResponse} serverResponse successful response from Ad Server
194
   * @param {ServerRequest} bidRequest
195
   * @return {Bid[]} an array of bids
196
   */
197
  interpretResponse: function (serverResponse, bidRequest) {
198
    const bids = [];
4✔
199
    const data = serverResponse.body;
4✔
200
    if (data.length > 0) {
4✔
201
      data.forEach(response => {
4✔
202
        if (response.cpm > 0) {
4✔
203
          bids.push(createNewBannerBid(response));
2✔
204
        }
205
      });
206
    }
207
    if (data.seatbid) {
4✔
208
      const seatbids = data.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []);
1✔
209
      seatbids.forEach(bid => bids.push(createNewVideoBid(bid, bidRequest)));
1✔
210
    }
211
    return bids;
4✔
212
  },
213

214
  getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') {
3!
215
    const syncs = [];
3✔
216
    const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`;
3!
217
    const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`;
3✔
218
    const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`;
3✔
219
    const pbCookieAssistSyncUrl = `${PB_COOKIE_ASSIST_SYNC_ENDPOINT}?${usPrivacy}${gdprFlag}${gdprString}`;
3✔
220

221
    if (syncOptions.iframeEnabled) {
3✔
222
      syncs.push({
1✔
223
        type: 'iframe',
224
        url: pbCookieAssistSyncUrl + '&type=iframe'
225
      });
226
    } else if (syncOptions.pixelEnabled) {
2✔
227
      syncs.push({
1✔
228
        type: 'image',
229
        url: pbCookieAssistSyncUrl + '&type=image'
230
      });
231
    }
232
    return syncs;
3✔
233
  }
234
};
235
registerBidder(spec);
1✔
236

237
/***************************************
238
 * Helper Functions
239
 ***************************************/
240

241
/**
242
 * @param {BidRequest} bidRequest bid request
243
 */
244
function hasBannerMediaType(bidRequest) {
245
  return !!deepAccess(bidRequest, 'mediaTypes.banner');
73✔
246
}
247

248
/**
249
 * @param {BidRequest} bidRequest bid request
250
 */
251
function hasVideoMediaType(bidRequest) {
252
  return !!deepAccess(bidRequest, 'mediaTypes.video');
81✔
253
}
254

255
/**
256
 * Adds placement information to array
257
 * @param request bid request
258
 */
259
function addPlacement(request) {
260
  const gpid = deepAccess(request, 'ortb2Imp.ext.gpid');
33✔
261
  const tagid = deepAccess(request, 'ortb2Imp.ext.tagid');
33✔
262
  const divid = deepAccess(request, 'ortb2Imp.ext.divid');
33✔
263
  const placementInfo = {
33✔
264
    placement_id: request.adUnitCode,
265
    callback_id: request.bidId,
266
    sizes: request.mediaTypes.banner.sizes
267
  };
268
  if (request.params) {
33✔
269
    if (request.params.placementId) {
33✔
270
      placementInfo.ym_placement_id = request.params.placementId;
3✔
271
    }
272
    const bidfloor = getBidFloor(request, BANNER);
33✔
273
    if (bidfloor) {
33✔
274
      placementInfo.bidFloor = bidfloor;
33✔
275
    }
276
  }
277
  if (gpid) {
33✔
278
    placementInfo.gpid = gpid;
1✔
279
  }
280
  if (tagid) {
33!
281
    placementInfo.tagid = tagid;
×
282
  }
283
  if (divid) {
33!
284
    placementInfo.divid = divid;
×
285
  }
286

287
  // get the transaction id for the banner bid.
288
  const transactionId = deepAccess(request, 'ortb2Imp.ext.tid');
33✔
289

290
  if (transactionId) {
33✔
291
    placementInfo.tid = transactionId;
1✔
292
  }
293
  if (request.auctionId) {
33✔
294
    // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
295
    placementInfo.auctionId = request.auctionId;
33✔
296
  }
297
  return JSON.stringify(placementInfo);
33✔
298
}
299

300
/**
301
 * creates a new banner bid with response information
302
 * @param response server response
303
 */
304
function createNewBannerBid(response) {
305
  return {
2✔
306
    dealId: response.publisherDealId,
307
    requestId: response['callback_id'],
308
    cpm: response.cpm,
309
    width: response.width,
310
    height: response.height,
311
    creativeId: response.creative_id,
312
    currency: CURRENCY,
313
    netRevenue: NET_REVENUE,
314
    ttl: TIME_TO_LIVE,
315
    ad: response.ad,
316
    meta: {
317
      advertiserDomains: response.adomain || [],
2!
318
      mediaType: BANNER,
319
    },
320
  };
321
}
322

323
/**
324
 * creates a new video bid with response information
325
 * @param response openRTB server response
326
 * @param bidRequest server request
327
 */
328
function createNewVideoBid(response, bidRequest) {
329
  const imp = (deepAccess(bidRequest, 'data.imp') || []).find(imp => imp.id === response.impid);
1!
330

331
  const result = {
1✔
332
    dealId: response.dealid,
333
    requestId: imp.id,
334
    cpm: response.price,
335
    width: imp.video.w,
336
    height: imp.video.h,
337
    creativeId: response.crid || response.adid,
1!
338
    currency: CURRENCY,
339
    netRevenue: NET_REVENUE,
340
    mediaType: VIDEO,
341
    ttl: TIME_TO_LIVE,
342
    vastXml: response.adm,
343
    meta: {
344
      advertiserDomains: response.adomain || [],
1!
345
      mediaType: VIDEO,
346
    },
347
  };
348
  if (imp.video.plcmt && imp.video.plcmt !== 1) {
1!
349
    const renderer = Renderer.install({
×
350
      url: OUTSTREAM_VIDEO_PLAYER_URL,
351
      config: {
352
        width: result.width,
353
        height: result.height,
354
        vastTimeout: VAST_TIMEOUT,
355
        maxAllowedVastTagRedirects: 5,
356
        allowVpaid: true,
357
        autoPlay: true,
358
        preload: true,
359
        mute: true,
360
      },
361
      id: imp.tagid,
362
      loaded: false,
363
    });
364

365
    renderer.setRender(function (bid) {
×
366
      bid.renderer.push(() => {
×
367
        const { id, config } = bid.renderer;
×
368
        window.YMoutstreamPlayer(bid, id, config);
×
369
      });
370
    });
371

372
    result.renderer = renderer;
×
373
  }
374

375
  return result;
1✔
376
}
377

378
/**
379
 * Detects whether dnt is true
380
 * @returns true if user enabled dnt
381
 */
382
function getDNT() {
383
  return (
29✔
384
    window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false
87✔
385
  );
386
}
387

388
/**
389
 * get page description
390
 */
391
function getPageDescription() {
392
  if (document.querySelector('meta[name="description"]')) {
29!
393
    return document
×
394
      .querySelector('meta[name="description"]')
395
      .getAttribute('content') || ''; // Value of the description metadata from the publisher's page.
396
  } else {
397
    return '';
29✔
398
  }
399
}
400

401
/**
402
 * Gets an id from the userId object if it exists
403
 * @param {*} request
404
 * @param {*} idType
405
 * @returns an id if there is one, or undefined
406
 */
407
function getId(request, idType) {
408
  return (typeof deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined;
99!
409
}
410

411
/**
412
 * @param {BidRequest[]} bidRequests bid request object
413
 * @param {BidderRequest} bidderRequest bidder request object
414
 * @return Object OpenRTB request object
415
 */
416
function openRtbRequest(bidRequests, bidderRequest) {
417
  const schain = bidRequests[0]?.ortb2?.source?.ext?.schain;
28✔
418
  const openRtbRequest = {
28✔
419
    id: bidRequests[0].bidderRequestId,
420
    tmax: bidderRequest.timeout || 400,
56✔
421
    at: 1,
422
    imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)),
30✔
423
    site: openRtbSite(bidRequests[0], bidderRequest),
424
    device: deepAccess(bidderRequest, 'ortb2.device'),
425
    badv: bidRequests[0].params.badv || [],
56✔
426
    bcat: deepAccess(bidderRequest, 'bcat') || bidRequests[0].params.bcat || [],
84✔
427
    ext: {
428
      prebid: '$prebid.version$',
429
    },
430
    ats_envelope: bidRequests[0].params.lr_env,
431
  };
432

433
  if (schain) {
28✔
434
    openRtbRequest.schain = schain;
1✔
435
  }
436
  const gpc = getGPCSignal(bidderRequest);
28✔
437
  if (gpc) {
28✔
438
    deepSetValue(openRtbRequest, 'regs.ext.gpc', gpc);
1✔
439
  }
440
  if (bidRequests[0].auctionId) {
28✔
441
    openRtbRequest.auctionId = bidRequests[0].auctionId;
28✔
442
  }
443
  populateOpenRtbGdpr(openRtbRequest, bidderRequest);
28✔
444
  return openRtbRequest;
28✔
445
}
446

447
function getGPCSignal(bidderRequest) {
448
  const gpc = deepAccess(bidderRequest, 'ortb2.regs.ext.gpc');
57✔
449
  return gpc;
57✔
450
}
451

452
function getCdep(bidderRequest) {
453
  const cdep = deepAccess(bidderRequest, 'ortb2.device.ext.cdep') || null;
57✔
454
  return cdep;
57✔
455
}
456

457
function getTopics(bidderRequest) {
458
  const userData = deepAccess(bidderRequest, 'ortb2.user.data') || [];
57✔
459
  const topicsData = userData.filter((dataObj) => {
57✔
460
    const segtax = dataObj.ext?.segtax;
2✔
461
    return segtax >= 600 && segtax <= 609;
2✔
462
  })[0];
463

464
  if (topicsData) {
57✔
465
    const topicsObject = {
2✔
466
      taxonomy: topicsData.ext.segtax,
467
      classifier: topicsData.ext.segclass,
468
      // topics needs to be array of numbers
469
      topics: Object.values(topicsData.segment).map(i => Number(i)),
6✔
470
    };
471
    return topicsObject;
2✔
472
  }
473
  return null;
55✔
474
}
475

476
/**
477
 * @param {BidRequest} bidRequest bidder request object.
478
 * @return Object OpenRTB's 'imp' (impression) object
479
 */
480
function openRtbImpression(bidRequest) {
481
  const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid');
30✔
482
  const tagid = deepAccess(bidRequest, 'ortb2Imp.ext.tagid');
30✔
483
  const divid = deepAccess(bidRequest, 'ortb2Imp.ext.divid');
30✔
484
  const size = extractPlayerSize(bidRequest);
30✔
485
  const imp = {
30✔
486
    id: bidRequest.bidId,
487
    tagid: bidRequest.adUnitCode,
488
    bidfloor: getBidFloor(bidRequest, VIDEO),
489
    ext: {
490
      placement_id: bidRequest.params.placementId,
491
      tid: deepAccess(bidRequest, 'ortb2Imp.ext.tid')
492
    },
493
    video: {
494
      w: size[0],
495
      h: size[1],
496
      linearity: 1
497
    }
498
  };
499

500
  const mediaTypesParams = deepAccess(bidRequest, 'mediaTypes.video', {});
30✔
501
  Object.keys(mediaTypesParams)
30✔
502
    .filter(param => OPENRTB_VIDEO_BIDPARAMS.includes(param))
105✔
503
    .forEach(param => {
504
      imp.video[param] = mediaTypesParams[param];
44✔
505
    });
506

507
  const videoParams = deepAccess(bidRequest, 'params.video', {});
30✔
508
  Object.keys(videoParams)
30✔
509
    .filter(param => OPENRTB_VIDEO_BIDPARAMS.includes(param))
211✔
510
    .forEach(param => {
511
      imp.video[param] = videoParams[param];
182✔
512
    });
513

514
  if (imp.video.skippable) {
30✔
515
    imp.video.skip = 1;
3✔
516
    delete imp.video.skippable;
3✔
517
  }
518
  if (imp.video.plcmt !== 1 || imp.video.placement !== 1) {
30!
519
    imp.video.startdelay = DEFAULT_START_DELAY;
30✔
520
    imp.video.playbackmethod = [ DEFAULT_PLAYBACK_METHOD ];
30✔
521
  }
522
  if (gpid) {
30✔
523
    imp.ext.gpid = gpid;
1✔
524
  }
525
  if (tagid) {
30!
526
    imp.ext.tagid = tagid;
×
527
  }
528
  if (divid) {
30!
529
    imp.ext.divid = divid;
×
530
  }
531
  return imp;
30✔
532
}
533

534
function getBidFloor(bidRequest, mediaType) {
535
  let floorInfo = {};
63✔
536

537
  if (typeof bidRequest.getFloor === 'function') {
63✔
538
    floorInfo = bidRequest.getFloor({ currency: CURRENCY, mediaType, size: '*' });
4✔
539
  }
540

541
  return floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.bidFloor || 0;
63✔
542
}
543

544
/**
545
 * @param {BidRequest} bidRequest bidder request object.
546
 * @return [number, number] || null Player's width and height, or undefined otherwise.
547
 */
548
function extractPlayerSize(bidRequest) {
549
  const sizeArr = deepAccess(bidRequest, 'mediaTypes.video.playerSize');
30✔
550
  if (isArrayOfNums(sizeArr, 2)) {
30!
551
    return sizeArr;
30✔
552
  } else if (isArray(sizeArr) && isArrayOfNums(sizeArr[0], 2)) {
×
553
    return sizeArr[0];
×
554
  }
555
  return null;
×
556
}
557

558
/**
559
 * @param {BidRequest} bidRequest bid request object
560
 * @param {BidderRequest} bidderRequest bidder request object
561
 * @return Object OpenRTB's 'site' object
562
 */
563
function openRtbSite(bidRequest, bidderRequest) {
564
  const result = {};
28✔
565

566
  const loc = parseUrl(deepAccess(bidderRequest, 'refererInfo.page'));
28✔
567
  if (!isEmpty(loc)) {
28✔
568
    result.page = `${loc.protocol}://${loc.hostname}${loc.pathname}`;
28✔
569
  }
570

571
  if (bidderRequest.refererInfo?.ref) {
28!
572
    result.ref = bidderRequest.refererInfo.ref;
×
573
  }
574

575
  const keywords = document.getElementsByTagName('meta')['keywords'];
28✔
576
  if (keywords && keywords.content) {
28!
577
    result.keywords = keywords.content;
×
578
  }
579

580
  const siteParams = deepAccess(bidRequest, 'params.site');
28✔
581
  if (siteParams) {
28!
582
    Object.keys(siteParams)
×
583
      .filter(param => OPENRTB_VIDEO_SITEPARAMS.includes(param))
×
584
      .forEach(param => {
NEW
585
        result[param] = siteParams[param];
×
586
      });
587
  }
588
  return result;
28✔
589
}
590

591
/**
592
 * Updates openRtbRequest with GDPR info from bidderRequest, if present.
593
 * @param {Object} openRtbRequest OpenRTB's request to update.
594
 * @param {BidderRequest} bidderRequest bidder request object.
595
 */
596
function populateOpenRtbGdpr(openRtbRequest, bidderRequest) {
597
  const gdpr = bidderRequest.gdprConsent;
28✔
598
  const gpp = deepAccess(bidderRequest, 'gppConsent.gppString');
28✔
599
  const gppsid = deepAccess(bidderRequest, 'gppConsent.applicableSections');
28✔
600
  if (gpp) {
28!
601
    deepSetValue(openRtbRequest, 'regs.ext.gpp', gpp);
×
602
  } else {
603
    deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr && gdpr.gdprApplies ? 1 : 0);
28✔
604
    deepSetValue(openRtbRequest, 'user.ext.consent', gdpr && gdpr.consentString ? gdpr.consentString : '');
28✔
605
  }
606
  if (gppsid && gppsid.length > 0) {
28!
607
    deepSetValue(openRtbRequest, 'regs.ext.gpp_sid', gppsid);
×
608
  }
609
  const uspConsent = deepAccess(bidderRequest, 'uspConsent');
28✔
610
  if (!gpp && uspConsent) {
28!
611
    deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent);
×
612
  }
613
}
614

615
/**
616
 * Determines whether or not the given video bid request is valid. If it's not a video bid, returns true.
617
 * @param {object} bid bid to validate
618
 * @return {boolean} true if valid, otherwise false
619
 */
620
function validateVideoParams(bid) {
621
  if (!hasVideoMediaType(bid)) {
9✔
622
    return true;
1✔
623
  }
624

625
  const paramRequired = (paramStr, value, conditionStr) => {
8✔
626
    let error = `"${paramStr}" is required`;
6✔
627
    if (conditionStr) {
6!
628
      error += ' when ' + conditionStr;
×
629
    }
630
    throw new Error(error);
6✔
631
  };
632

633
  const paramInvalid = (paramStr, value, expectedStr) => {
8✔
634
    expectedStr = expectedStr ? ', expected: ' + expectedStr : '';
1!
635
    value = JSON.stringify(value);
1✔
636
    throw new Error(`"${paramStr}"=${value} is invalid${expectedStr}`);
1✔
637
  };
638

639
  const isDefined = val => typeof val !== 'undefined';
46✔
640
  const validate = (fieldPath, validateCb, errorCb, errorCbParam) => {
8✔
641
    if (fieldPath.indexOf('video') === 0) {
55✔
642
      const valueFieldPath = 'params.' + fieldPath;
45✔
643
      const mediaFieldPath = 'mediaTypes.' + fieldPath;
45✔
644
      const valueParams = deepAccess(bid, valueFieldPath);
45✔
645
      const mediaTypesParams = deepAccess(bid, mediaFieldPath);
45✔
646
      const hasValidValueParams = validateCb(valueParams);
45✔
647
      const hasValidMediaTypesParams = validateCb(mediaTypesParams);
45✔
648

649
      if (hasValidValueParams) return valueParams;
45✔
650
      else if (hasValidMediaTypesParams) return hasValidMediaTypesParams;
30✔
651
      else {
652
        if (!hasValidValueParams) errorCb(valueFieldPath, valueParams, errorCbParam);
6!
653
        else if (!hasValidMediaTypesParams) errorCb(mediaFieldPath, mediaTypesParams, errorCbParam);
×
654
      }
655
      return valueParams || mediaTypesParams;
×
656
    } else {
657
      const value = deepAccess(bid, fieldPath);
10✔
658
      if (!validateCb(value)) {
10✔
659
        errorCb(fieldPath, value, errorCbParam);
1✔
660
      }
661
      return value;
9✔
662
    }
663
  };
664

665
  try {
8✔
666
    validate('video.context', val => !isEmpty(val), paramRequired);
16✔
667

668
    validate('params.placementId', val => !isEmpty(val), paramRequired);
8✔
669

670
    validate('video.playerSize', val => isArrayOfNums(val, 2) ||
14!
671
      (isArray(val) && val.every(v => isArrayOfNums(v, 2))),
×
672
    paramInvalid, 'array of 2 integers, ex: [640,480] or [[640,480]]');
673

674
    validate('video.mimes', val => isDefined(val), paramRequired);
12✔
675
    validate('video.mimes', val => isArray(val) && val.every(v => isStr(v)), paramInvalid,
10✔
676
      'array of strings, ex: ["video/mp4"]');
677
    validate('video.protocols', val => isDefined(val), paramRequired);
10✔
678

679
    validate('video.api', val => isDefined(val), paramRequired);
6✔
680
    // PS-6597 - Allow video.api to be any number greater than 0
681
    validate('video.api', val => isArrayOfNums(val) && val.every(v => (v >= 1)),
4✔
682
      paramInvalid, 'array of numbers, ex: [2,3]');
683

684
    validate('video.playbackmethod', val => !isDefined(val) || isArrayOfNums(val), paramInvalid,
4✔
685
      'array of integers, ex: [2,6]');
686

687
    validate('video.maxduration', val => isDefined(val), paramRequired);
4✔
688
    validate('video.maxduration', val => isInteger(val), paramInvalid);
2✔
689
    validate('video.minduration', val => !isDefined(val) || isNumber(val), paramInvalid);
2!
690
    validate('video.skippable', val => !isDefined(val) || isBoolean(val), paramInvalid);
2!
691
    validate('video.skipafter', val => !isDefined(val) || isNumber(val), paramInvalid);
2!
692
    validate('video.pos', val => !isDefined(val) || isNumber(val), paramInvalid);
2!
693
    validate('params.badv', val => !isDefined(val) || isArray(val), paramInvalid,
1!
694
      'array of strings, ex: ["ford.com","pepsi.com"]');
695
    validate('params.bcat', val => !isDefined(val) || isArray(val), paramInvalid,
1!
696
      'array of strings, ex: ["IAB1-5","IAB1-6"]');
697
    return true;
1✔
698
  } catch (e) {
699
    logError(e.message);
7✔
700
    return false;
7✔
701
  }
702
}
703

704
/**
705
 * Shortcut object property and check if required characters count was deleted
706
 *
707
 * @param {number} extraCharacters count of characters to remove
708
 * @param {object} target object on which string property length should be reduced
709
 * @param {string} propertyName name of property to reduce
710
 * @return {number} 0 if required characters count was removed otherwise count of how many left
711
 */
712
function shortcutProperty(extraCharacters, target, propertyName) {
713
  if (target[propertyName].length > extraCharacters) {
4✔
714
    target[propertyName] = target[propertyName].substring(0, target[propertyName].length - extraCharacters);
2✔
715

716
    return 0
2✔
717
  }
718

719
  const charactersLeft = extraCharacters - target[propertyName].length;
2✔
720

721
  target[propertyName] = '';
2✔
722

723
  return charactersLeft;
2✔
724
}
725

726
/**
727
 * Creates and returnes eids arr using createEidsArray from './userId/eids.js' module;
728
 * @param {Object} bidRequest OpenRTB's request as a cource of userId.
729
 * @return array of eids objects
730
 */
731
function getEids(bidRequest) {
732
  if (deepAccess(bidRequest, 'userIdAsEids')) {
57✔
733
    return bidRequest.userIdAsEids || [];
3!
734
  }
735
};
736

737
/**
738
 * Check if top window can be accessed
739
 *
740
 * @return {boolean} true if can access top window otherwise false
741
 */
742
function canAccessTopWindow() {
743
  try {
29✔
744
    if (getWindowTop().location.href) {
29✔
745
      return true;
29✔
746
    }
747
  } catch (error) {
748
    return false;
×
749
  }
750
}
751

752
function isStage(bidderRequest) {
753
  return !!bidderRequest.refererInfo?.referer?.includes('pb_force_a');
57✔
754
}
755

756
function getAdserverUrl(path, stage) {
757
  const domain = stage ? STAGE_DOMAIN : PROD_DOMAIN;
114!
758
  return `${domain}${path}`;
114✔
759
}
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