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

prebid / Prebid.js / 18755158999

23 Oct 2025 04:26PM UTC coverage: 96.232% (-0.002%) from 96.234%
18755158999

push

github

web-flow
browsiRtdProvider: do not init analytics module (#13883)

* browsiRtdProvider-analytics-module

* browsiRtdProvider-analytics-module

* Update browsiRtdProvider.js

---------

Co-authored-by: Stav Ben Shlomo <stavbe@gobrowsi.com>
Co-authored-by: Patrick McCann <pmccann@cafemedia.com>
Co-authored-by: Patrick McCann <patmmccann@gmail.com>

52544 of 64372 branches covered (81.63%)

201018 of 208888 relevant lines covered (96.23%)

125.09 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/navigatorData/dnt.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
  ],
111
  supportedMediaTypes: [BANNER, VIDEO, NATIVE],
112

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

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

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

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

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

226
      return prBid;
8✔
227
    });
228
  },
229

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

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

257
registerBidder(spec);
1✔
258

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

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

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

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

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

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

364
function getDefinedParamsOrEmpty(object, params) {
365
  if (object === undefined) {
54✔
366
    return {};
11✔
367
  }
368
  return getDefinedParams(object, params);
43✔
369
}
370

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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