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

prebid / Prebid.js / 21010614790

14 Jan 2026 09:31PM UTC coverage: 96.211% (-0.001%) from 96.212%
21010614790

push

github

web-flow
Revert "Optable RTD Module: Read cache for targeting data (#14291)" (#14340)

This reverts commit 01c1b595d.

41518 of 51043 branches covered (81.34%)

36 of 38 new or added lines in 2 files covered. (94.74%)

185 existing lines in 10 files now uncovered.

207825 of 216010 relevant lines covered (96.21%)

71.35 hits per line

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

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

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

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

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

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

61
/**
62
 * Adapter for requesting bids from AdKernel white-label display platform
63
 */
64
export const spec = {
1✔
65
  code: 'adkernel',
66
  gvlid: GVLID,
67
  aliases: [
68
    {code: 'headbidding'},
69
    {code: 'adsolut'},
70
    {code: 'oftmediahb'},
71
    {code: 'audiencemedia'},
72
    {code: 'waardex_ak'},
73
    {code: 'roqoon'},
74
    {code: 'adbite'},
75
    {code: 'houseofpubs'},
76
    {code: 'torchad'},
77
    {code: 'stringads'},
78
    {code: 'bcm'},
79
    {code: 'engageadx'},
80
    {code: 'converge', gvlid: 248},
81
    {code: 'adomega'},
82
    {code: 'denakop'},
83
    {code: 'rtbanalytica'},
84
    {code: 'unibots'},
85
    {code: 'ergadx'},
86
    {code: 'turktelekom'},
87
    {code: 'motionspots'},
88
    {code: 'sonic_twist'},
89
    {code: 'displayioads'},
90
    {code: 'rtbdemand_com'},
91
    {code: 'bidbuddy'},
92
    {code: 'didnadisplay'},
93
    {code: 'qortex'},
94
    {code: 'adpluto'},
95
    {code: 'headbidder'},
96
    {code: 'digiad'},
97
    {code: 'monetix'},
98
    {code: 'hyperbrainz'},
99
    {code: 'voisetech'},
100
    {code: 'global_sun'},
101
    {code: 'rxnetwork'},
102
    {code: 'revbid'},
103
    {code: 'spinx', gvlid: 1308},
104
    {code: 'oppamedia'},
105
    {code: 'pixelpluses', gvlid: 1209},
106
    {code: 'urekamedia'},
107
    {code: 'smartyexchange'},
108
    {code: 'infinety'},
109
    {code: 'qohere'},
110
    {code: 'blutonic'},
111
    {code: 'appmonsta', gvlid: 1283}
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);
25✔
140
    const requests = [];
25✔
141
    const schain = bidRequests[0]?.ortb2?.source?.ext?.schain;
25✔
142
    _each(impGroups, impGroup => {
25✔
143
      const {host, zoneId, imps} = impGroup;
27✔
144
      const request = buildRtbRequest(imps, bidderRequest, schain);
27✔
145
      requests.push({
27✔
146
        method: 'POST',
147
        url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`,
148
        data: JSON.stringify(request)
149
      });
150
    });
151
    return requests;
25✔
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);
25✔
268
  return Object.values(
25✔
269
    bidRequests.map(bidRequest => buildImps(bidRequest, secure))
27✔
270
      .reduce((acc, curr, index) => {
271
        const bidRequest = bidRequests[index];
27✔
272
        const {zoneId, host} = bidRequest.params;
27✔
273
        const key = `${host}_${zoneId}`;
27✔
274
        acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []};
27✔
275
        acc[key].imps.push(...curr);
27✔
276
        return acc;
27✔
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 = {
27✔
288
    'id': bidRequest.bidId,
289
    'tagid': bidRequest.adUnitCode
290
  };
291
  if (secure) {
27✔
292
    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
27✔
293
  }
294
  var sizes = [];
27✔
295
  const mediaTypes = bidRequest.mediaTypes;
27✔
296
  const isMultiformat = (~~!!mediaTypes?.banner + ~~!!mediaTypes?.video + ~~!!mediaTypes?.native) > 1;
27✔
297
  const result = [];
27✔
298
  let typedImp;
299

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

319
  if (mediaTypes?.video) {
27✔
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!
334
    } else if (pbVideo.w && pbVideo.h) {
×
335
      typedImp.video.w = pbVideo.w;
×
UNCOV
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) {
27✔
343
    if (isMultiformat) {
2!
344
      typedImp = {...imp};
×
UNCOV
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;
27✔
357
}
358

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

366
function getDefinedParamsOrEmpty(object, params) {
367
  if (object === undefined) {
54✔
368
    return {};
11✔
369
  }
370
  return getDefinedParams(object, params);
43✔
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')) {
27✔
395
    return;
24✔
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({
27✔
412
    'ip': 'caller',
413
    'ipv6': 'caller',
414
    'ua': 'caller',
415
    'js': 1,
416
    'language': getLanguage()
417
  }, fpd.device || {});
54✔
418
  if (getDNT()) {
27✔
419
    device.dnt = 1;
26✔
420
  }
421
  return {device: device};
27✔
422
}
423

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

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

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

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

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

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

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

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

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

576
function getExtendedUserIds(bidderRequest) {
577
  const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids');
27✔
578
  if (isArray(eids)) {
27✔
579
    return eids;
1✔
580
  }
581
}
582

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

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

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

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

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