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

prebid / Prebid.js / 22200455621

19 Feb 2026 09:16PM UTC coverage: 96.215% (+0.01%) from 96.205%
22200455621

Pull #14467

github

46b23c
dgirardi
lint
Pull Request #14467: Prebid 11: add `adUnit.element` option

54203 of 66555 branches covered (81.44%)

119 of 142 new or added lines in 50 files covered. (83.8%)

428 existing lines in 44 files now uncovered.

208120 of 216307 relevant lines covered (96.22%)

69.05 hits per line

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

94.59
/modules/adkernelBidAdapter.js
1
import {
1✔
2
  _each,
3
  contains,
4
  createTrackPixelHtml,
5
  deepAccess,
6
  deepSetValue,
7
  getDefinedParams,
8
  isArray,
9
  isArrayOfNums,
10
  isEmpty,
11
  isNumber,
12
  isPlainObject,
13
  isStr,
14
  mergeDeep,
15
  parseGPTSingleSizeArrayToRtbSize,
16
  triggerPixel
17
} from '../src/utils.js';
18
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
19
import {registerBidder} from '../src/adapters/bidderFactory.js';
20
import {config} from '../src/config.js';
21
import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js';
22
import {getBidFloor} from '../libraries/adkernelUtils/adkernelUtils.js';
23

24
/**
25
 * In case you're AdKernel whitelable platform's client who needs branded adapter to
26
 * work with Adkernel platform - DO NOT COPY THIS ADAPTER UNDER NEW NAME
27
 *
28
 * Please contact prebid@adkernel.com and we'll add your adapter as an alias
29
 * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
30
 * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest
31
 * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync
32
 */
33

34
const VIDEO_PARAMS = ['pos', 'context', 'placement', 'plcmt', 'api', 'mimes', 'protocols', 'playbackmethod', 'minduration', 'maxduration',
1✔
35
  'startdelay', 'linearity', 'skip', 'skipmin', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackend', 'boxingallowed'];
36
const VIDEO_FPD = ['battr', 'pos'];
1✔
37
const NATIVE_FPD = ['battr', 'api'];
1✔
38
const BANNER_PARAMS = ['pos'];
1✔
39
const BANNER_FPD = ['btype', 'battr', 'pos', 'api'];
1✔
40
const VERSION = '1.8';
1✔
41
const SYNC_IFRAME = 1;
1✔
42
const SYNC_IMAGE = 2;
1✔
43
const SYNC_TYPES = {
1✔
44
  1: 'iframe',
45
  2: 'image'
46
};
47
const GVLID = 14;
1✔
48

49
const MULTI_FORMAT_SUFFIX = '__mf';
1✔
50
const MULTI_FORMAT_SUFFIX_BANNER = 'b' + MULTI_FORMAT_SUFFIX;
1✔
51
const MULTI_FORMAT_SUFFIX_VIDEO = 'v' + MULTI_FORMAT_SUFFIX;
1✔
52
const MULTI_FORMAT_SUFFIX_NATIVE = 'n' + MULTI_FORMAT_SUFFIX;
1✔
53

54
const MEDIA_TYPES = {
1✔
55
  BANNER: 1,
56
  VIDEO: 2,
57
  NATIVE: 4
58
};
59

60
/**
61
 * Adapter for requesting bids from AdKernel white-label display platform
62
 */
63
export const spec = {
1✔
64
  code: 'adkernel',
65
  gvlid: GVLID,
66
  aliases: [
67
    {code: 'headbidding'},
68
    {code: 'adsolut'},
69
    {code: 'oftmediahb'},
70
    {code: 'audiencemedia'},
71
    {code: 'waardex_ak'},
72
    {code: 'roqoon'},
73
    {code: 'adbite'},
74
    {code: 'houseofpubs'},
75
    {code: 'torchad'},
76
    {code: 'stringads'},
77
    {code: 'bcm'},
78
    {code: 'engageadx'},
79
    {code: 'converge', gvlid: 248},
80
    {code: 'adomega'},
81
    {code: 'denakop'},
82
    {code: 'rtbanalytica'},
83
    {code: 'unibots'},
84
    {code: 'ergadx'},
85
    {code: 'turktelekom'},
86
    {code: 'motionspots'},
87
    {code: 'sonic_twist'},
88
    {code: 'displayioads'},
89
    {code: 'rtbdemand_com'},
90
    {code: 'bidbuddy'},
91
    {code: 'didnadisplay'},
92
    {code: 'qortex'},
93
    {code: 'adpluto'},
94
    {code: 'headbidder'},
95
    {code: 'digiad'},
96
    {code: 'monetix'},
97
    {code: 'hyperbrainz'},
98
    {code: 'voisetech'},
99
    {code: 'global_sun'},
100
    {code: 'rxnetwork'},
101
    {code: 'revbid'},
102
    {code: 'spinx', gvlid: 1308},
103
    {code: 'oppamedia'},
104
    {code: 'pixelpluses', gvlid: 1209},
105
    {code: 'urekamedia'},
106
    {code: 'smartyexchange'},
107
    {code: 'infinety'},
108
    {code: 'qohere'},
109
    {code: 'blutonic'},
110
    {code: 'appmonsta', gvlid: 1283},
111
    {code: 'intlscoop'}
112
  ],
113
  supportedMediaTypes: [BANNER, VIDEO, NATIVE],
114

115
  /**
116
   * Validates bid request for adunit
117
   * @param bidRequest {BidRequest}
118
   * @returns {boolean}
119
   */
120
  isBidRequestValid: function (bidRequest) {
121
    return 'params' in bidRequest &&
5✔
122
      typeof bidRequest.params.host !== 'undefined' &&
123
      'zoneId' in bidRequest.params &&
124
      !isNaN(Number(bidRequest.params.zoneId)) &&
125
      bidRequest.params.zoneId > 0 &&
126
      bidRequest.mediaTypes &&
127
      (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video ||
128
        (bidRequest.mediaTypes.native && validateNativeAdUnit(bidRequest.mediaTypes.native))
129
      );
130
  },
131

132
  /**
133
   * Builds http request for each unique combination of adkernel host/zone
134
   * @param bidRequests {BidRequest[]}
135
   * @param bidderRequest {BidderRequest}
136
   * @returns {ServerRequest[]}
137
   */
138
  buildRequests: function (bidRequests, bidderRequest) {
139
    const impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo);
24✔
140
    const requests = [];
24✔
141
    const schain = bidRequests[0]?.ortb2?.source?.ext?.schain;
24!
142
    _each(impGroups, impGroup => {
24✔
143
      const {host, zoneId, imps} = impGroup;
26✔
144
      const request = buildRtbRequest(imps, bidderRequest, schain);
26✔
145
      requests.push({
26✔
146
        method: 'POST',
147
        url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`,
148
        data: JSON.stringify(request)
149
      });
150
    });
151
    return requests;
24✔
152
  },
153

154
  /**
155
   * Parse response from adkernel backend
156
   * @param serverResponse {ServerResponse}
157
   * @param serverRequest {ServerRequest}
158
   * @returns {Bid[]}
159
   */
160
  interpretResponse: function (serverResponse, serverRequest) {
161
    const response = serverResponse.body;
8✔
162
    if (!response.seatbid) {
8✔
163
      return [];
1✔
164
    }
165

166
    const rtbRequest = JSON.parse(serverRequest.data);
7✔
167
    const rtbBids = response.seatbid
7✔
168
      .map(seatbid => seatbid.bid)
7✔
169
      .reduce((a, b) => a.concat(b), []);
7✔
170

171
    return rtbBids.map(rtbBid => {
7✔
172
      const imp = ((rtbRequest.imp) || []).find(imp => imp.id === rtbBid.impid);
9!
173
      const prBid = {
8✔
174
        requestId: rtbBid.impid,
175
        cpm: rtbBid.price,
176
        creativeId: rtbBid.crid,
177
        currency: response.cur || 'USD',
13✔
178
        ttl: 360,
179
        netRevenue: true
180
      };
181
      if (prBid.requestId.endsWith(MULTI_FORMAT_SUFFIX)) {
8✔
182
        prBid.requestId = stripMultiformatSuffix(prBid.requestId);
2✔
183
      }
184
      if (rtbBid.mtype === MEDIA_TYPES.BANNER) {
8✔
185
        prBid.mediaType = BANNER;
3✔
186
        prBid.width = rtbBid.w;
3✔
187
        prBid.height = rtbBid.h;
3✔
188
        prBid.ad = formatAdMarkup(rtbBid);
3✔
189
      } else if (rtbBid.mtype === MEDIA_TYPES.VIDEO) {
5✔
190
        prBid.mediaType = VIDEO;
4✔
191
        if (rtbBid.adm) {
4✔
192
          prBid.vastXml = rtbBid.adm;
2✔
193
          if (rtbBid.nurl) {
2✔
194
            prBid.nurl = rtbBid.nurl;
2✔
195
          }
196
        } else {
197
          prBid.vastUrl = rtbBid.nurl;
2✔
198
        }
199
        prBid.width = imp.video.w;
4✔
200
        prBid.height = imp.video.h;
4✔
201
      } else if (rtbBid.mtype === MEDIA_TYPES.NATIVE) {
1✔
202
        prBid.mediaType = NATIVE;
1✔
203
        prBid.native = {
1✔
204
          ortb: buildNativeAd(rtbBid.adm)
205
        };
206
      }
207
      if (isStr(rtbBid.dealid)) {
8✔
208
        prBid.dealId = rtbBid.dealid;
2✔
209
      }
210
      if (isArray(rtbBid.adomain)) {
8✔
211
        deepSetValue(prBid, 'meta.advertiserDomains', rtbBid.adomain);
1✔
212
      }
213
      if (isArray(rtbBid.cat)) {
8✔
214
        deepSetValue(prBid, 'meta.secondaryCatIds', rtbBid.cat);
1✔
215
      }
216
      if (isPlainObject(rtbBid.ext)) {
8✔
217
        if (isNumber(rtbBid.ext.advertiser_id)) {
1✔
218
          deepSetValue(prBid, 'meta.advertiserId', rtbBid.ext.advertiser_id);
1✔
219
        }
220
        if (isStr(rtbBid.ext.advertiser_name)) {
1✔
221
          deepSetValue(prBid, 'meta.advertiserName', rtbBid.ext.advertiser_name);
1✔
222
        }
223
        if (isStr(rtbBid.ext.agency_name)) {
1✔
224
          deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name);
1✔
225
        }
226
      }
227

228
      return prBid;
8✔
229
    });
230
  },
231

232
  /**
233
   * Extracts user-syncs information from server response
234
   * @param syncOptions {SyncOptions}
235
   * @param serverResponses {ServerResponse[]}
236
   * @returns {UserSync[]}
237
   */
238
  getUserSyncs: function (syncOptions, serverResponses) {
239
    if (!serverResponses || serverResponses.length === 0 || (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled)) {
4✔
240
      return [];
2✔
241
    }
242
    return serverResponses.filter(rsp => rsp.body && rsp.body.ext && rsp.body.ext.adk_usersync)
2✔
243
      .map(rsp => rsp.body.ext.adk_usersync)
2✔
244
      .reduce((a, b) => a.concat(b), [])
2✔
245
      .map(({url, type}) => ({type: SYNC_TYPES[type], url: url}));
2✔
246
  },
247

248
  /**
249
   * Handle bid win
250
   * @param bid {Bid}
251
   */
252
  onBidWon: function (bid) {
253
    if (bid.nurl) {
1✔
254
      triggerPixel(bid.nurl);
1✔
255
    }
256
  }
257
};
258

259
registerBidder(spec);
1✔
260

261
/**
262
 * Dispatch impressions by ad network host and zone
263
 * @param bidRequests {BidRequest[]}
264
 * @param refererInfo {refererInfo}
265
 */
266
function groupImpressionsByHostZone(bidRequests, refererInfo) {
267
  const secure = (refererInfo && refererInfo.page?.indexOf('https:') === 0);
24!
268
  return Object.values(
24✔
269
    bidRequests.map(bidRequest => buildImps(bidRequest, secure))
26✔
270
      .reduce((acc, curr, index) => {
271
        const bidRequest = bidRequests[index];
26✔
272
        const {zoneId, host} = bidRequest.params;
26✔
273
        const key = `${host}_${zoneId}`;
26✔
274
        acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []};
26✔
275
        acc[key].imps.push(...curr);
26✔
276
        return acc;
26✔
277
      }, {})
278
  );
279
}
280

281
/**
282
 *  Builds rtb imp object(s) for single adunit
283
 *  @param bidRequest {BidRequest}
284
 *  @param secure {boolean}
285
 */
286
function buildImps(bidRequest, secure) {
287
  const imp = {
26✔
288
    'id': bidRequest.bidId,
289
    'tagid': bidRequest.adUnitCode
290
  };
291
  if (secure) {
26✔
292
    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
26!
293
  }
294
  var sizes = [];
26✔
295
  const mediaTypes = bidRequest.mediaTypes;
26✔
296
  const isMultiformat = (~~!!mediaTypes?.banner + ~~!!mediaTypes?.video + ~~!!mediaTypes?.native) > 1;
26✔
297
  const result = [];
26✔
298
  let typedImp;
299

300
  if (mediaTypes?.banner) {
26✔
301
    if (isMultiformat) {
20✔
302
      typedImp = {...imp};
1✔
303
      typedImp.id = imp.id + MULTI_FORMAT_SUFFIX_BANNER;
1✔
304
    } else {
305
      typedImp = imp;
19✔
306
    }
307
    sizes = getAdUnitSizes(bidRequest);
20✔
308
    const pbBanner = mediaTypes.banner;
20✔
309
    typedImp.banner = {
20✔
310
      ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, BANNER_FPD),
311
      ...getDefinedParamsOrEmpty(pbBanner, BANNER_PARAMS),
312
      format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)),
37✔
313
      topframe: 0
314
    };
315
    initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : BANNER);
20✔
316
    result.push(typedImp);
20✔
317
  }
318

319
  if (mediaTypes?.video) {
26✔
320
    if (isMultiformat) {
5✔
321
      typedImp = {...imp};
1✔
322
      typedImp.id = typedImp.id + MULTI_FORMAT_SUFFIX_VIDEO;
1✔
323
    } else {
324
      typedImp = imp;
4✔
325
    }
326
    const pbVideo = mediaTypes.video;
5✔
327
    typedImp.video = {
5✔
328
      ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, VIDEO_FPD),
329
      ...getDefinedParamsOrEmpty(pbVideo, VIDEO_PARAMS)
330
    };
331
    if (pbVideo.playerSize) {
5!
332
      sizes = pbVideo.playerSize[0];
5✔
333
      typedImp.video = Object.assign(typedImp.video, parseGPTSingleSizeArrayToRtbSize(sizes) || {});
5!
UNCOV
334
    } else if (pbVideo.w && pbVideo.h) {
×
335
      typedImp.video.w = pbVideo.w;
×
336
      typedImp.video.h = pbVideo.h;
×
337
    }
338
    initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : VIDEO);
5✔
339
    result.push(typedImp);
5✔
340
  }
341

342
  if (mediaTypes?.native) {
26✔
343
    if (isMultiformat) {
2!
UNCOV
344
      typedImp = {...imp};
×
345
      typedImp.id = typedImp.id + MULTI_FORMAT_SUFFIX_NATIVE;
×
346
    } else {
347
      typedImp = imp;
2✔
348
    }
349
    typedImp.native = {
2✔
350
      ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, NATIVE_FPD),
351
      request: JSON.stringify(bidRequest.nativeOrtbRequest)
352
    };
353
    initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : NATIVE);
2!
354
    result.push(typedImp);
2✔
355
  }
356
  return result;
26✔
357
}
358

359
function initImpBidfloor(imp, bid, sizes, mediaType) {
360
  const bidfloor = getBidFloor(bid, mediaType, sizes);
27✔
361
  if (bidfloor) {
27✔
362
    imp.bidfloor = bidfloor;
1✔
363
  }
364
}
365

366
function getDefinedParamsOrEmpty(object, params) {
367
  if (object === undefined) {
52✔
368
    return {};
11✔
369
  }
370
  return getDefinedParams(object, params);
41✔
371
}
372

373
/**
374
 * Checks if configuration allows specified sync method
375
 * @param syncRule {Object}
376
 * @param bidderCode {string}
377
 * @returns {boolean}
378
 */
379
function isSyncMethodAllowed(syncRule, bidderCode) {
380
  if (!syncRule) {
7✔
381
    return false;
2✔
382
  }
383
  const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode];
5✔
384
  const rule = syncRule.filter === 'include';
5✔
385
  return contains(bidders, bidderCode) === rule;
5✔
386
}
387

388
/**
389
 * Get preferred user-sync method based on publisher configuration
390
 * @param bidderCode {string}
391
 * @returns {number|undefined}
392
 */
393
function getAllowedSyncMethod(bidderCode) {
394
  if (!config.getConfig('userSync.syncEnabled')) {
26✔
395
    return;
23✔
396
  }
397
  const filterConfig = config.getConfig('userSync.filterSettings');
3✔
398
  if (isSyncMethodAllowed(filterConfig.all, bidderCode) || isSyncMethodAllowed(filterConfig.iframe, bidderCode)) {
3✔
399
    return SYNC_IFRAME;
1✔
400
  } else if (isSyncMethodAllowed(filterConfig.image, bidderCode)) {
2✔
401
    return SYNC_IMAGE;
1✔
402
  }
403
}
404

405
/**
406
 * Create device object from fpd and host-collected data
407
 * @param fpd {Object}
408
 * @returns {{device: Object}}
409
 */
410
function makeDevice(fpd) {
411
  const device = mergeDeep({
26✔
412
    'ip': 'caller',
413
    'ipv6': 'caller',
414
    'ua': 'caller',
415
    'js': 1,
416
    'language': getLanguage()
417
  }, fpd.device || {});
52✔
418
  return {device: device};
26✔
419
}
420

421
/**
422
 * Create site or app description object
423
 * @param bidderRequest {BidderRequest}
424
 * @param fpd {Object}
425
 * @returns {{site: Object}|{app: Object}}
426
 */
427
function makeSiteOrApp(bidderRequest, fpd) {
428
  const {refererInfo} = bidderRequest;
26✔
429
  const appConfig = config.getConfig('app');
26✔
430
  if (isEmpty(appConfig)) {
26!
431
    return {site: createSite(refererInfo, fpd)}
26✔
432
  } else {
UNCOV
433
    return {app: appConfig};
×
434
  }
435
}
436

437
/**
438
 * Create user description object
439
 * @param bidderRequest {BidderRequest}
440
 * @param fpd {Object}
441
 * @returns {{user: Object} | undefined}
442
 */
443
function makeUser(bidderRequest, fpd) {
444
  const {gdprConsent} = bidderRequest;
26✔
445
  const user = fpd.user || {};
26✔
446
  if (gdprConsent && gdprConsent.consentString !== undefined) {
26✔
447
    deepSetValue(user, 'ext.consent', gdprConsent.consentString);
1✔
448
  }
449
  const eids = getExtendedUserIds(bidderRequest);
26✔
450
  if (eids) {
26✔
451
    deepSetValue(user, 'ext.eids', eids);
1✔
452
  }
453
  if (!isEmpty(user)) {
26✔
454
    return {user: user};
2✔
455
  }
456
}
457

458
/**
459
 * Create privacy regulations object
460
 * @param bidderRequest {BidderRequest}
461
 * @returns {{regs: Object} | undefined}
462
 */
463
function makeRegulations(bidderRequest) {
464
  const {gdprConsent, uspConsent, gppConsent} = bidderRequest;
26✔
465
  const regs = {};
26✔
466
  if (gdprConsent) {
26✔
467
    if (gdprConsent.gdprApplies !== undefined) {
2✔
468
      deepSetValue(regs, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies);
2✔
469
    }
470
  }
471
  if (gppConsent) {
26✔
472
    deepSetValue(regs, 'regs.gpp', gppConsent.gppString);
1✔
473
    deepSetValue(regs, 'regs.gpp_sid', gppConsent.applicableSections);
1✔
474
  }
475
  if (uspConsent) {
26✔
476
    deepSetValue(regs, 'regs.ext.us_privacy', uspConsent);
1✔
477
  }
478
  if (config.getConfig('coppa')) {
26✔
479
    deepSetValue(regs, 'regs.coppa', 1);
1✔
480
  }
481
  if (!isEmpty(regs)) {
26✔
482
    return regs;
3✔
483
  }
484
}
485

486
/**
487
 * Create top-level request object
488
 * @param bidderRequest {BidderRequest}
489
 * @param imps {Object} Impressions
490
 * @param fpd {Object} First party data
491
 * @returns
492
 */
493
function makeBaseRequest(bidderRequest, imps, fpd) {
494
  const request = {
26✔
495
    'id': bidderRequest.bidderRequestId,
496
    'imp': imps,
497
    'at': 1,
498
    'tmax': parseInt(bidderRequest.timeout)
499
  };
500
  if (!isEmpty(fpd.bcat)) {
26!
UNCOV
501
    request.bcat = fpd.bcat;
×
502
  }
503
  if (!isEmpty(fpd.badv)) {
26!
UNCOV
504
    request.badv = fpd.badv;
×
505
  }
506
  return request;
26✔
507
}
508

509
/**
510
 * Initialize sync capabilities
511
 * @param bidderRequest {BidderRequest}
512
 */
513
function makeSyncInfo(bidderRequest) {
514
  const {bidderCode} = bidderRequest;
26✔
515
  const syncMethod = getAllowedSyncMethod(bidderCode);
26✔
516
  if (syncMethod) {
26✔
517
    const res = {};
2✔
518
    deepSetValue(res, 'ext.adk_usersync', syncMethod);
2✔
519
    return res;
2✔
520
  }
521
}
522

523
/**
524
 * Builds complete rtb request
525
 * @param imps {Object} Collection of rtb impressions
526
 * @param bidderRequest {BidderRequest}
527
 * @param schain {Object=} Supply chain config
528
 * @return {Object} Complete rtb request
529
 */
530
function buildRtbRequest(imps, bidderRequest, schain) {
531
  const fpd = bidderRequest.ortb2 || {};
26✔
532

533
  const req = mergeDeep(
26✔
534
    makeBaseRequest(bidderRequest, imps, fpd),
535
    makeDevice(fpd),
536
    makeSiteOrApp(bidderRequest, fpd),
537
    makeUser(bidderRequest, fpd),
538
    makeRegulations(bidderRequest),
539
    makeSyncInfo(bidderRequest)
540
  );
541
  if (schain) {
26!
UNCOV
542
    deepSetValue(req, 'source.ext.schain', schain);
×
543
  }
544
  return req;
26✔
545
}
546

547
/**
548
 * Get browser language
549
 * @returns {String}
550
 */
551
function getLanguage() {
552
  const language = navigator.language ? 'language' : 'userLanguage';
26!
553
  return navigator[language].split('-')[0];
26✔
554
}
555

556
/**
557
 * Creates site description object
558
 */
559
function createSite(refInfo, fpd) {
560
  const site = {
26✔
561
    'domain': refInfo.domain,
562
    'page': refInfo.page
563
  };
564
  mergeDeep(site, fpd.site);
26✔
565
  if (refInfo.ref != null) {
26!
UNCOV
566
    site.ref = refInfo.ref;
×
567
  } else {
568
    delete site.ref;
26✔
569
  }
570
  return site;
26✔
571
}
572

573
function getExtendedUserIds(bidderRequest) {
574
  const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids');
26✔
575
  if (isArray(eids)) {
26✔
576
    return eids;
1✔
577
  }
578
}
579

580
/**
581
 *  Format creative with optional nurl call
582
 *  @param bid rtb Bid object
583
 */
584
function formatAdMarkup(bid) {
585
  let adm = bid.adm;
3✔
586
  if ('nurl' in bid) {
3✔
587
    adm += createTrackPixelHtml(`${bid.nurl}&px=1`);
3✔
588
  }
589
  return adm;
3✔
590
}
591

592
/**
593
 * Basic validates to comply with platform requirements
594
 */
595
function validateNativeAdUnit(adUnit) {
596
  return validateNativeImageSize(adUnit.image) && validateNativeImageSize(adUnit.icon) &&
1✔
597
    !deepAccess(adUnit, 'privacyLink.required') && // not supported yet
598
    !deepAccess(adUnit, 'privacyIcon.required'); // not supported yet
599
}
600

601
/**
602
 * Validates image asset size definition
603
 */
604
function validateNativeImageSize(img) {
605
  if (!img) {
2!
UNCOV
606
    return true;
×
607
  }
608
  if (img.sizes) {
2✔
609
    return isArrayOfNums(img.sizes, 2);
1✔
610
  }
611
  if (isArray(img.aspect_ratios)) {
1✔
612
    return img.aspect_ratios.length > 0 && img.aspect_ratios[0].min_height && img.aspect_ratios[0].min_width;
1✔
613
  }
UNCOV
614
  return true;
×
615
}
616

617
/**
618
 * Creates native ad for native 1.2 response
619
 */
620
function buildNativeAd(adm) {
621
  let resp = JSON.parse(adm);
1✔
622
  // temporary workaround for top-level native object wrapper
623
  if ('native' in resp) {
1✔
624
    resp = resp.native;
1✔
625
  }
626
  return resp;
1✔
627
}
628

629
function stripMultiformatSuffix(impid) {
630
  return impid.substr(0, impid.length - MULTI_FORMAT_SUFFIX.length - 1);
2✔
631
}
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