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

prebid / Prebid.js / 21444465511

28 Jan 2026 03:32PM UTC coverage: 96.22% (+0.003%) from 96.217%
21444465511

push

github

web-flow
Taboola support extra signals (#14299)

* add deferredBilling support using onBidBillable

* update burl setting

* support nurl firing logic

* add extra signals to taboola request

* add extra ad signals

* fix missing semicolon

* use Prebid's built-in counters

* updated detectBot logic

41716 of 51289 branches covered (81.34%)

216 of 221 new or added lines in 2 files covered. (97.74%)

130 existing lines in 9 files now uncovered.

209165 of 217382 relevant lines covered (96.22%)

71.73 hits per line

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

96.36
/modules/taboolaBidAdapter.js
1
'use strict';
1✔
2

3
import {registerBidder} from '../src/adapters/bidderFactory.js';
4
import {BANNER} from '../src/mediaTypes.js';
5
import {config} from '../src/config.js';
6
import {deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject, getWinDimensions} from '../src/utils.js';
7
import {getStorageManager} from '../src/storageManager.js';
8
import {ajax} from '../src/ajax.js';
9
import {ortbConverter} from '../libraries/ortbConverter/converter.js';
10
import {getConnectionType} from '../libraries/connectionInfo/connectionUtils.js';
11
import {getViewportCoordinates} from '../libraries/viewport/viewport.js';
12
import {percentInView} from '../libraries/percentInView/percentInView.js';
13
import {getBoundingClientRect} from '../libraries/boundingClientRect/boundingClientRect.js';
14

15
const BIDDER_CODE = 'taboola';
1✔
16
const GVLID = 42;
1✔
17
const CURRENCY = 'USD';
1✔
18
export const END_POINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction';
1✔
19
export const USER_SYNC_IMG_URL = 'https://trc.taboola.com/sg/prebidJS/1/cm';
1✔
20
export const USER_SYNC_IFRAME_URL = 'https://cdn.taboola.com/scripts/prebid_iframe_sync.html';
1✔
21
const USER_ID = 'user-id';
1✔
22
const STORAGE_KEY = `taboola global:${USER_ID}`;
1✔
23
const COOKIE_KEY = 'trc_cookie_storage';
1✔
24
const TGID_COOKIE_KEY = 't_gid';
1✔
25
const TGID_PT_COOKIE_KEY = 't_pt_gid';
1✔
26
const TBLA_ID_COOKIE_KEY = 'tbla_id';
1✔
27
export const EVENT_ENDPOINT = 'https://beacon.bidder.taboola.com';
1✔
28

29
/**
30
 *  extract User Id by that order:
31
 * 1. local storage
32
 * 2. first party cookie
33
 * 3. rendered trc
34
 * 4. new user set it to 0
35
 */
36
export const userData = {
1✔
37
  storageManager: getStorageManager({bidderCode: BIDDER_CODE}),
38
  getUserId: () => {
39
    const {getFromLocalStorage, getFromCookie, getFromTRC} = userData;
55✔
40

41
    try {
55✔
42
      return getFromLocalStorage() || getFromCookie() || getFromTRC();
55✔
43
    } catch (ex) {
44
      return 0;
1✔
45
    }
46
  },
47
  getFromCookie() {
48
    const {cookiesAreEnabled, getCookie} = userData.storageManager;
53✔
49
    if (cookiesAreEnabled()) {
53✔
50
      const cookieData = getCookie(COOKIE_KEY);
7✔
51
      let userId;
52
      if (cookieData) {
7✔
53
        userId = userData.getCookieDataByKey(cookieData, USER_ID);
5✔
54
      }
55
      if (userId) {
7✔
56
        return userId;
3✔
57
      }
58
      userId = getCookie(TGID_COOKIE_KEY);
4✔
59
      if (userId) {
4✔
60
        return userId;
2✔
61
      }
62
      userId = getCookie(TGID_PT_COOKIE_KEY);
2✔
63
      if (userId) {
2✔
64
        return userId;
1✔
65
      }
66
      const tblaId = getCookie(TBLA_ID_COOKIE_KEY);
1✔
67
      if (tblaId) {
1✔
68
        return tblaId;
1✔
69
      }
70
    }
71
    return undefined;
46✔
72
  },
73
  getCookieDataByKey(cookieData, key) {
74
    if (!cookieData) {
5!
75
      return undefined;
×
76
    }
77
    const [, value = ''] = cookieData.split(`${key}=`)
5✔
78
    return value;
5✔
79
  },
80
  getFromLocalStorage() {
81
    const {hasLocalStorage, localStorageIsEnabled, getDataFromLocalStorage} = userData.storageManager;
55✔
82

83
    if (hasLocalStorage() && localStorageIsEnabled()) {
55✔
84
      return getDataFromLocalStorage(STORAGE_KEY);
1✔
85
    }
86
    return undefined;
53✔
87
  },
88
  getFromTRC() {
89
    return window.TRC ? window.TRC.user_id : 0;
46✔
90
  }
91
}
92

93
export const internal = {
1✔
94
  getPageUrl: (refererInfo = {}) => {
58✔
95
    return refererInfo?.page || getWindowSelf().location.href;
58✔
96
  },
97
  getReferrer: (refererInfo = {}) => {
58✔
98
    return refererInfo?.ref || getWindowSelf().document.referrer;
58✔
99
  }
100
}
101

102
export function detectBot() {
103
  try {
57✔
104
    return {
57✔
105
      detected: !!(
106
        window.__nightmare ||
228✔
107
        window.callPhantom ||
108
        window._phantom ||
109
        /HeadlessChrome/.test(navigator.userAgent)
110
      )
111
    };
112
  } catch (e) {
NEW
113
    return { detected: false };
×
114
  }
115
}
116

117
export function getPageVisibility() {
118
  try {
59✔
119
    return {
59✔
120
      hidden: document.hidden,
121
      state: document.visibilityState,
122
      hasFocus: document.hasFocus()
123
    };
124
  } catch (e) {
NEW
125
    return { hidden: false, state: 'visible', hasFocus: true };
×
126
  }
127
}
128

129
export function getDeviceExtSignals(existingExt = {}) {
55✔
130
  const viewport = getViewportCoordinates();
55✔
131
  return {
55✔
132
    ...existingExt,
133
    bot: detectBot(),
134
    visibility: getPageVisibility(),
135
    scroll: {
136
      top: Math.round(viewport.top),
137
      left: Math.round(viewport.left)
138
    }
139
  };
140
}
141

142
export function getElementSignals(adUnitCode) {
143
  try {
59✔
144
    const element = document.getElementById(adUnitCode);
59✔
145
    if (!element) return null;
59✔
146

147
    const rect = getBoundingClientRect(element);
3✔
148
    const winDimensions = getWinDimensions();
3✔
149
    const rawViewability = percentInView(element);
3✔
150

151
    const signals = {
3✔
152
      placement: {
153
        top: Math.round(rect.top),
154
        left: Math.round(rect.left)
155
      },
156
      fold: rect.top < winDimensions.innerHeight ? 'above' : 'below'
3!
157
    };
158

159
    if (rawViewability !== null && !isNaN(rawViewability)) {
3✔
160
      signals.viewability = Math.round(rawViewability);
3✔
161
    }
162

163
    return signals;
3✔
164
  } catch (e) {
NEW
165
    return null;
×
166
  }
167
}
168

169
const converter = ortbConverter({
1✔
170
  context: {
171
    netRevenue: true,
172
    mediaType: BANNER,
173
    ttl: 300
174
  },
175
  imp(buildImp, bidRequest, context) {
176
    const imp = buildImp(bidRequest, context);
59✔
177
    fillTaboolaImpData(bidRequest, imp);
59✔
178
    return imp;
59✔
179
  },
180
  request(buildRequest, imps, bidderRequest, context) {
181
    const reqData = buildRequest(imps, bidderRequest, context);
55✔
182
    fillTaboolaReqData(bidderRequest, context.bidRequests[0], reqData, context);
55✔
183
    return reqData;
55✔
184
  },
185
  bidResponse(buildBidResponse, bid, context) {
186
    const bidResponse = buildBidResponse(bid, context);
11✔
187
    bidResponse.nurl = bid.nurl;
11✔
188
    if (bid.burl) {
11!
189
      bidResponse.burl = bid.burl;
×
190
    }
191
    bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price);
11✔
192
    if (bid.ext && bid.ext.dchain) {
11✔
193
      deepSetValue(bidResponse, 'meta.dchain', bid.ext.dchain);
1✔
194
    }
195
    return bidResponse
11✔
196
  }
197
});
198

199
export const spec = {
1✔
200
  supportedMediaTypes: [BANNER],
201
  gvlid: GVLID,
202
  code: BIDDER_CODE,
203
  isBidRequestValid: (bidRequest) => {
204
    return !!(bidRequest.sizes &&
4✔
205
              bidRequest.params &&
206
              bidRequest.params.publisherId &&
207
              bidRequest.params.tagId);
208
  },
209
  buildRequests: (validBidRequests, bidderRequest) => {
210
    const [bidRequest] = validBidRequests;
55✔
211
    const auctionId = bidderRequest.auctionId || validBidRequests[0]?.auctionId;
55✔
212
    const data = converter.toORTB({
55✔
213
      bidderRequest: bidderRequest,
214
      bidRequests: validBidRequests,
215
      context: { auctionId }
216
    });
217
    const {publisherId} = bidRequest.params;
55✔
218
    const url = END_POINT_URL + '?publisher=' + publisherId;
55✔
219

220
    return {
55✔
221
      url,
222
      method: 'POST',
223
      data: data,
224
      bids: validBidRequests,
225
      options: {
226
        withCredentials: false
227
      },
228
    };
229
  },
230
  interpretResponse: (serverResponse, request) => {
231
    if (!request || !request.bids || !request.data) {
13✔
232
      return [];
1✔
233
    }
234

235
    if (!serverResponse || !serverResponse.body) {
12✔
236
      return [];
1✔
237
    }
238
    const bids = [];
11✔
239
    const fledgeAuctionConfigs = [];
11✔
240
    if (!serverResponse.body.seatbid || !serverResponse.body.seatbid.length || !serverResponse.body.seatbid[0].bid || !serverResponse.body.seatbid[0].bid.length) {
11✔
241
      if (!serverResponse.body.ext || !serverResponse.body.ext.igbid || !serverResponse.body.ext.igbid.length) {
2!
242
        return [];
2✔
243
      }
244
    } else {
245
      bids.push(...converter.fromORTB({response: serverResponse.body, request: request.data}).bids);
9✔
246
    }
247
    if (isArray(serverResponse.body.ext?.igbid)) {
9✔
248
      serverResponse.body.ext.igbid.forEach((igbid) => {
4✔
249
        if (!igbid || !igbid.igbuyer || !igbid.igbuyer.length || !igbid.igbuyer[0].buyerdata) {
4✔
250
          return;
2✔
251
        }
252
        const buyerdata = safeJSONParse(igbid.igbuyer[0]?.buyerdata)
2✔
253
        if (!buyerdata) {
2!
254
          return;
×
255
        }
256
        const perBuyerSignals = {};
2✔
257
        igbid.igbuyer.forEach(buyerItem => {
2✔
258
          if (!buyerItem || !buyerItem.buyerdata || !buyerItem.origin) {
2!
259
            return;
×
260
          }
261
          const parsedData = safeJSONParse(buyerItem.buyerdata)
2✔
262
          if (!parsedData || !parsedData.perBuyerSignals || !(buyerItem.origin in parsedData.perBuyerSignals)) {
2✔
263
            return;
1✔
264
          }
265
          perBuyerSignals[buyerItem.origin] = parsedData.perBuyerSignals[buyerItem.origin];
1✔
266
        });
267
        const impId = igbid?.impid;
2✔
268
        fledgeAuctionConfigs.push({
2✔
269
          impId,
270
          config: {
271
            seller: buyerdata?.seller,
272
            resolveToConfig: buyerdata?.resolveToConfig,
273
            sellerSignals: {},
274
            sellerTimeout: buyerdata?.sellerTimeout,
275
            perBuyerSignals,
276
            auctionSignals: {},
277
            decisionLogicUrl: buyerdata?.decisionLogicUrl,
278
            interestGroupBuyers: buyerdata?.interestGroupBuyers,
279
            perBuyerTimeouts: buyerdata?.perBuyerTimeouts,
280
          },
281
        });
282
      });
283
    }
284

285
    if (fledgeAuctionConfigs.length) {
9✔
286
      return {
2✔
287
        bids,
288
        paapi: fledgeAuctionConfigs,
289
      };
290
    }
291
    return bids;
7✔
292
  },
293
  onBidWon: (bid) => {
294
    if (bid.nurl && !bid.deferBilling) {
2✔
295
      const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.originalCpm);
1✔
296
      ajax(resolvedNurl);
1✔
297
      bid.taboolaBillingFired = true;
1✔
298
    }
299
  },
300
  onBidBillable: (bid) => {
301
    if (bid.taboolaBillingFired) {
3!
302
      return;
×
303
    }
304
    const billingUrl = bid.burl || bid.nurl;
3✔
305
    if (billingUrl) {
3✔
306
      const resolvedBillingUrl = replaceAuctionPrice(billingUrl, bid.originalCpm);
2✔
307
      ajax(resolvedBillingUrl);
2✔
308
    }
309
  },
310
  getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) {
311
    const syncs = []
10✔
312
    const queryParams = [];
10✔
313
    if (gdprConsent) {
10✔
314
      queryParams.push(`gdpr=${Number(gdprConsent.gdprApplies && 1)}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`);
3✔
315
    }
316

317
    if (uspConsent) {
10✔
318
      queryParams.push('us_privacy=' + encodeURIComponent(uspConsent));
4✔
319
    }
320

321
    if (gppConsent) {
10✔
322
      queryParams.push('gpp=' + encodeURIComponent(gppConsent.gppString || '') + '&gpp_sid=' + encodeURIComponent((gppConsent.applicableSections || []).join(',')));
2!
323
    }
324

325
    if (syncOptions.iframeEnabled) {
10✔
326
      syncs.push({
2✔
327
        type: 'iframe',
328
        url: USER_SYNC_IFRAME_URL + (queryParams.length ? '?' + queryParams.join('&') : '')
2!
329
      });
330
    }
331

332
    if (syncOptions.pixelEnabled) {
10✔
333
      syncs.push({
8✔
334
        type: 'image',
335
        url: USER_SYNC_IMG_URL + (queryParams.length ? '?' + queryParams.join('&') : '')
8✔
336
      });
337
    }
338
    return syncs;
10✔
339
  },
340
  onTimeout: (timeoutData) => {
341
    ajax(EVENT_ENDPOINT + '/timeout', null, JSON.stringify(timeoutData), {method: 'POST'});
1✔
342
  },
343

344
  onBidderError: ({ error, bidderRequest }) => {
1✔
345
    ajax(EVENT_ENDPOINT + '/bidError', null, JSON.stringify({error, bidderRequest}), {method: 'POST'});
1✔
346
  },
347
};
348

349
function getSiteProperties({publisherId}, refererInfo, ortb2) {
55✔
350
  const {getPageUrl, getReferrer} = internal;
55✔
351
  return {
55✔
352
    id: publisherId,
353
    name: publisherId,
354
    domain: ortb2?.site?.domain || refererInfo?.domain || window.location?.host,
112✔
355
    page: ortb2?.site?.page || getPageUrl(refererInfo),
110✔
356
    ref: ortb2?.site?.ref || getReferrer(refererInfo),
110✔
357
    publisher: {
358
      id: publisherId
359
    },
360
    content: {
361
      language: navigator.language
362
    }
363
  }
364
}
365

366
function fillTaboolaReqData(bidderRequest, bidRequest, data, context) {
367
  const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest;
55✔
368
  const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2);
55✔
369

370
  const ortb2Device = bidderRequest?.ortb2?.device || {};
55✔
371
  const connectionType = getConnectionType();
55✔
372
  const device = {
55✔
373
    ...ortb2Device,
374
    js: 1,
375
    ...(connectionType && { connectiontype: connectionType }),
56✔
376
    ext: getDeviceExtSignals(ortb2Device.ext)
377
  };
378
  deepSetValue(data, 'device', device);
55✔
379
  const extractedUserId = userData.getUserId(gdprConsent, uspConsent);
55✔
380
  if (data.user === undefined || data.user === null) {
55✔
381
    data.user = {
51✔
382
      buyeruid: 0,
383
      ext: {}
384
    }
385
  }
386
  if (extractedUserId && extractedUserId !== 0) {
55✔
387
    deepSetValue(data, 'user.buyeruid', extractedUserId);
9✔
388
  }
389
  if (data.regs?.ext === undefined || data.regs?.ext === null) {
55!
390
    data.regs = {
55✔
391
      ext: {}
392
    }
393
  }
394
  deepSetValue(data, 'regs.coppa', 0);
55✔
395
  if (gdprConsent.gdprApplies) {
55✔
396
    deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
1✔
397
    deepSetValue(data, 'regs.ext.gdpr', 1);
1✔
398
  }
399
  if (uspConsent) {
55✔
400
    deepSetValue(data, 'regs.ext.us_privacy', uspConsent);
1✔
401
  }
402

403
  if (bidderRequest.ortb2?.regs?.gpp) {
55✔
404
    deepSetValue(data, 'regs.ext.gpp', bidderRequest.ortb2.regs.gpp);
1✔
405
    deepSetValue(data, 'regs.ext.gpp_sid', bidderRequest.ortb2.regs.gpp_sid);
1✔
406
  }
407

408
  if (config.getConfig('coppa')) {
55✔
409
    deepSetValue(data, 'regs.coppa', 1);
1✔
410
  }
411

412
  const ortb2 = bidderRequest.ortb2 || {
55✔
413
    bcat: [],
414
    badv: [],
415
    wlang: []
416
  };
417

418
  deepSetValue(data, 'source.fd', 1);
55✔
419

420
  data.id = bidderRequest.bidderRequestId;
55✔
421
  data.site = site;
55✔
422
  data.tmax = (bidderRequest.timeout === null || bidderRequest.timeout === undefined) ? undefined : parseInt(bidderRequest.timeout);
55✔
423
  data.bcat = ortb2.bcat || bidRequest.params.bcat || [];
55✔
424
  data.badv = ortb2.badv || bidRequest.params.badv || [];
55✔
425
  data.wlang = ortb2.wlang || bidRequest.params.wlang || [];
55✔
426
  deepSetValue(data, 'ext.pageType', ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType);
55✔
427
  deepSetValue(data, 'ext.prebid.version', '$prebid.version$');
55✔
428
  const auctionId = context?.auctionId;
55✔
429
  if (auctionId) {
55✔
430
    deepSetValue(data, 'ext.prebid.auctionId', auctionId);
55✔
431
  }
432
}
433

434
function fillTaboolaImpData(bid, imp) {
435
  const {tagId, position} = bid.params;
59✔
436
  imp.banner = getBanners(bid, position);
59✔
437
  imp.tagid = tagId;
59✔
438

439
  if (typeof bid.getFloor === 'function') {
59✔
440
    const floorInfo = bid.getFloor({
2✔
441
      currency: CURRENCY,
442
      size: '*'
443
    });
444
    if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
2✔
445
      imp.bidfloor = parseFloat(floorInfo.floor);
2✔
446
      imp.bidfloorcur = CURRENCY;
2✔
447
    }
448
  } else {
449
    const {bidfloor = null, bidfloorcur = CURRENCY} = bid.params;
57✔
450
    imp.bidfloor = bidfloor;
57✔
451
    imp.bidfloorcur = bidfloorcur;
57✔
452
  }
453
  deepSetValue(imp, 'ext.gpid', bid?.ortb2Imp?.ext?.gpid);
59✔
454

455
  if (bid.bidId) {
59✔
456
    deepSetValue(imp, 'ext.prebid.bidId', bid.bidId);
59✔
457
  }
458
  if (bid.adUnitCode) {
59✔
459
    deepSetValue(imp, 'ext.prebid.adUnitCode', bid.adUnitCode);
17✔
460
  }
461
  if (bid.adUnitId) {
59✔
462
    deepSetValue(imp, 'ext.prebid.adUnitId', bid.adUnitId);
1✔
463
  }
464

465
  deepSetValue(imp, 'ext.prebid.bidRequestsCount', bid.bidRequestsCount);
59✔
466
  deepSetValue(imp, 'ext.prebid.bidderRequestsCount', bid.bidderRequestsCount);
59✔
467
  deepSetValue(imp, 'ext.prebid.bidderWinsCount', bid.bidderWinsCount);
59✔
468

469
  const elementSignals = getElementSignals(bid.adUnitCode);
59✔
470
  if (elementSignals) {
59✔
471
    if (elementSignals.viewability !== undefined) {
3✔
472
      deepSetValue(imp, 'ext.viewability', elementSignals.viewability);
3✔
473
    }
474
    deepSetValue(imp, 'ext.placement', elementSignals.placement);
3✔
475
    deepSetValue(imp, 'ext.fold', elementSignals.fold);
3✔
476
  }
477
}
478

479
function getBanners(bid, pos) {
480
  return {
59✔
481
    ...getSizes(bid.sizes),
482
    pos: pos
483
  }
484
}
485

486
function getSizes(sizes) {
487
  return {
59✔
488
    format: sizes.map(size => {
489
      return {
94✔
490
        w: size[0],
491
        h: size[1]
492
      }
493
    })
494
  }
495
}
496

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

© 2026 Coveralls, Inc