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

prebid / Prebid.js / 19836249188

01 Dec 2025 08:19PM UTC coverage: 96.234% (+0.004%) from 96.23%
19836249188

push

github

web-flow
clickioBidAdapter: add IAB GVL ID and TCFEU support (#14224)

53490 of 65500 branches covered (81.66%)

1 of 1 new or added line in 1 file covered. (100.0%)

87 existing lines in 10 files now uncovered.

204130 of 212118 relevant lines covered (96.23%)

72.06 hits per line

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

81.88
/modules/datablocksBidAdapter.js
1
import {deepAccess, getWinDimensions, getWindowTop, isGptPubadsDefined} from '../src/utils.js';
1✔
2
import {registerBidder} from '../src/adapters/bidderFactory.js';
3
import {config} from '../src/config.js';
4
import {BANNER, NATIVE} from '../src/mediaTypes.js';
5
import {getStorageManager} from '../src/storageManager.js';
6
import {ajax} from '../src/ajax.js';
7
import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
8
import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js';
9
import {isWebdriverEnabled} from '../libraries/webdriver/webdriver.js';
10
import { buildNativeRequest, parseNativeResponse } from '../libraries/nativeAssetsUtils.js';
11

12
export const storage = getStorageManager({bidderCode: 'datablocks'});
1✔
13

14
// DEFINE THE PREBID BIDDER SPEC
15
export const spec = {
1✔
16
  supportedMediaTypes: [BANNER, NATIVE],
17
  code: 'datablocks',
18

19
  // DATABLOCKS SCOPED OBJECT
20
  db_obj: {metrics_host: 'prebid.dblks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0},
21

22
  // STORE THE DATABLOCKS BUYERID IN STORAGE
23
  store_dbid: function(dbid) {
24
    let stored = false;
2✔
25

26
    // CREATE 1 YEAR EXPIRY DATE
27
    const d = new Date();
2✔
28
    d.setTime(Date.now() + (365 * 24 * 60 * 60 * 1000));
2✔
29

30
    // TRY TO STORE IN COOKIE
31
    if (storage.cookiesAreEnabled) {
2✔
32
      storage.setCookie('_db_dbid', dbid, d.toUTCString(), 'None', null);
2✔
33
      stored = true;
2✔
34
    }
35

36
    // TRY TO STORE IN LOCAL STORAGE
37
    if (storage.localStorageIsEnabled) {
2✔
38
      storage.setDataInLocalStorage('_db_dbid', dbid);
2✔
39
      stored = true;
2✔
40
    }
41

42
    return stored;
2✔
43
  },
44

45
  // FETCH DATABLOCKS BUYERID FROM STORAGE
46
  get_dbid: function() {
47
    let dbId = '';
3✔
48
    if (storage.cookiesAreEnabled) {
3✔
49
      dbId = storage.getCookie('_db_dbid') || '';
3✔
50
    }
51

52
    if (!dbId && storage.localStorageIsEnabled) {
3✔
53
      dbId = storage.getDataFromLocalStorage('_db_dbid') || '';
3✔
54
    }
55
    return dbId;
3✔
56
  },
57

58
  // STORE SYNCS IN STORAGE
59
  store_syncs: function(syncs) {
60
    if (storage.localStorageIsEnabled) {
1✔
61
      const syncObj = {};
1✔
62
      syncs.forEach(sync => {
1✔
63
        syncObj[sync.id] = sync.uid;
1✔
64
      });
65

66
      // FETCH EXISTING SYNCS AND MERGE NEW INTO STORAGE
67
      const storedSyncs = this.get_syncs();
1✔
68
      storage.setDataInLocalStorage('_db_syncs', JSON.stringify(Object.assign(storedSyncs, syncObj)));
1✔
69

70
      return true;
1✔
71
    }
72
  },
73

74
  // GET SYNCS FROM STORAGE
75
  get_syncs: function() {
76
    if (storage.localStorageIsEnabled) {
3!
77
      const syncData = storage.getDataFromLocalStorage('_db_syncs');
3✔
78
      if (syncData) {
3!
UNCOV
79
        return JSON.parse(syncData);
×
80
      } else {
81
        return {};
3✔
82
      }
83
    } else {
UNCOV
84
      return {};
×
85
    }
86
  },
87

88
  // ADD METRIC DATA TO THE METRICS RESPONSE QUEUE
89
  queue_metric: function(metric) {
UNCOV
90
    if (typeof metric === 'object') {
×
91
      // PUT METRICS IN THE QUEUE
UNCOV
92
      this.db_obj.metrics.push(metric);
×
93

94
      // RESET PREVIOUS TIMER
UNCOV
95
      if (this.db_obj.metrics_timer) {
×
UNCOV
96
        clearTimeout(this.db_obj.metrics_timer);
×
97
      }
98

99
      // SETUP THE TIMER TO FIRE BACK THE DATA
UNCOV
100
      const scope = this;
×
UNCOV
101
      this.db_obj.metrics_timer = setTimeout(function() {
×
UNCOV
102
        scope.send_metrics();
×
103
      }, this.db_obj.metrics_queue_time);
104

UNCOV
105
      return true;
×
106
    } else {
UNCOV
107
      return false;
×
108
    }
109
  },
110

111
  // POST CONSOLIDATED METRICS BACK TO SERVER
112
  send_metrics: function() {
113
    // POST TO SERVER
UNCOV
114
    ajax(`https://${this.db_obj.metrics_host}/a/pb/`, null, JSON.stringify(this.db_obj.metrics), {method: 'POST', withCredentials: true});
×
115

116
    // RESET THE QUEUE OF METRIC DATA
UNCOV
117
    this.db_obj.metrics = [];
×
118

UNCOV
119
    return true;
×
120
  },
121

122
  // GET BASIC CLIENT INFORMATION
123
  get_client_info: function () {
124
    const botTest = new BotClientTests();
2✔
125
    const win = getWindowTop();
2✔
126
    const windowDimensions = getWinDimensions();
2✔
127
    return {
2✔
128
      'wiw': windowDimensions.innerWidth,
129
      'wih': windowDimensions.innerHeight,
130
      'saw': null,
131
      'sah': null,
132
      'scd': null,
133
      'sw': windowDimensions.screen.width,
134
      'sh': windowDimensions.screen.height,
135
      'whl': win.history.length,
136
      'wxo': win.pageXOffset,
137
      'wyo': win.pageYOffset,
138
      'wpr': win.devicePixelRatio,
139
      'is_bot': botTest.doTests(),
140
      'is_hid': win.document.hidden,
141
      'vs': win.document.visibilityState
142
    };
143
  },
144

145
  // LISTEN FOR GPT VIEWABILITY EVENTS
146
  get_viewability: function(bid) {
147
    // ONLY RUN ONCE IF PUBLISHER HAS OPTED IN
148
    if (!this.db_obj.vis_optout && !this.db_obj.vis_run) {
2✔
149
      this.db_obj.vis_run = true;
1✔
150

151
      // ADD GPT EVENT LISTENERS
152
      const scope = this;
1✔
153
      if (isGptPubadsDefined()) {
1✔
154
        if (typeof window['googletag'].pubads().addEventListener === 'function') {
1✔
155
          // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
156
          window['googletag'].pubads().addEventListener('impressionViewable', function(event) {
1✔
UNCOV
157
            scope.queue_metric({type: 'slot_view', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()});
×
158
          });
159
          window['googletag'].pubads().addEventListener('slotRenderEnded', function(event) {
1✔
160
            scope.queue_metric({type: 'slot_render', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()});
×
161
          })
162
        }
163
      }
164
    }
165
  },
166

167
  // VALIDATE THE BID REQUEST
168
  isBidRequestValid: function(bid) {
169
    // SET GLOBAL VARS FROM BIDDER CONFIG
170
    this.db_obj.source_id = bid.params.source_id;
3✔
171
    if (bid.params.vis_optout) {
3✔
172
      this.db_obj.vis_optout = true;
1✔
173
    }
174

175
    return !!(bid.params.source_id && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native));
3!
176
  },
177

178
  // GENERATE THE RTB REQUEST
179
  buildRequests: function(validRequests, bidderRequest) {
180
    // convert Native ORTB definition to old-style prebid native definition
181
    validRequests = convertOrtbRequestToProprietaryNative(validRequests);
2✔
182

183
    // RETURN EMPTY IF THERE ARE NO VALID REQUESTS
184
    if (!validRequests.length) {
2✔
185
      return [];
1✔
186
    }
187

188
    const imps = [];
1✔
189
    // ITERATE THE VALID REQUESTS AND GENERATE IMP OBJECT
190
    validRequests.forEach(bidRequest => {
1✔
191
      // BUILD THE IMP OBJECT
192
      const imp = {
3✔
193
        id: bidRequest.bidId,
194
        tagid: bidRequest.params.tagid || bidRequest.adUnitCode,
6✔
195
        placement_id: bidRequest.params.placement_id || 0,
6✔
196
        secure: window.location.protocol === 'https:',
197
        ortb2: deepAccess(bidRequest, `ortb2Imp`) || {},
6✔
198
        floor: {}
199
      }
200

201
      // CHECK FOR FLOORS
202
      if (typeof bidRequest.getFloor === 'function') {
3!
UNCOV
203
        imp.floor = bidRequest.getFloor({
×
204
          currency: 'USD',
205
          mediaType: '*',
206
          size: '*'
207
        });
208
      }
209

210
      // BUILD THE SIZES
211
      if (deepAccess(bidRequest, `mediaTypes.banner`)) {
3✔
212
        const sizes = getAdUnitSizes(bidRequest);
2✔
213
        if (sizes.length) {
2✔
214
          imp.banner = {
2✔
215
            w: sizes[0][0],
216
            h: sizes[0][1],
217
            format: sizes.map(size => ({ w: size[0], h: size[1] }))
2✔
218
          };
219

220
          // ADD TO THE LIST OF IMP REQUESTS
221
          imps.push(imp);
2✔
222
        }
223
      } else if (deepAccess(bidRequest, `mediaTypes.native`)) {
1✔
224
        // ADD TO THE LIST OF IMP REQUESTS
225
        imp.native = buildNativeRequest(bidRequest.nativeParams);
1✔
226
        imps.push(imp);
1✔
227
      }
228
    });
229

230
    // RETURN EMPTY IF THERE WERE NO PROPER ADUNIT REQUESTS TO BE MADE
231
    if (!imps.length) {
1!
UNCOV
232
      return [];
×
233
    }
234

235
    // GENERATE SITE OBJECT
236
    const site = {
1✔
237
      domain: window.location.host,
238
      // TODO: is 'page' the right value here?
239
      page: bidderRequest.refererInfo.page,
240
      schain: validRequests[0]?.ortb2?.source?.ext?.schain || {},
7!
241
      ext: {
242
        p_domain: bidderRequest.refererInfo.domain,
243
        rt: bidderRequest.refererInfo.reachedTop,
244
        frames: bidderRequest.refererInfo.numIframes,
245
        stack: bidderRequest.refererInfo.stack,
246
        timeout: config.getConfig('bidderTimeout')
247
      },
248
    };
249

250
    // ADD REF URL IF FOUND
251
    if (self === top && document.referrer) {
1!
UNCOV
252
      site.ref = document.referrer;
×
253
    }
254

255
    // ADD META KEYWORDS IF FOUND
256
    const keywords = document.getElementsByTagName('meta')['keywords'];
1✔
257
    if (keywords && keywords.content) {
1!
UNCOV
258
      site.keywords = keywords.content;
×
259
    }
260

261
    // GENERATE DEVICE OBJECT
262
    const device = {
1✔
263
      ip: 'peer',
264
      ua: window.navigator.userAgent,
265
      js: 1,
266
      language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en',
2!
267
      buyerid: this.get_dbid() || 0,
2✔
268
      ext: {
269
        pb_eids: validRequests[0].userIdAsEids || {},
2✔
270
        syncs: this.get_syncs() || {},
1!
271
        coppa: config.getConfig('coppa') || 0,
2✔
272
        gdpr: bidderRequest.gdprConsent || {},
2✔
273
        usp: bidderRequest.uspConsent || {},
2✔
274
        client_info: this.get_client_info(),
275
        ortb2: bidderRequest.ortb2 || {}
2✔
276
      }
277
    };
278

279
    const sourceId = validRequests[0].params.source_id || 0;
1!
280
    const host = validRequests[0].params.host || 'prebid.dblks.net';
1!
281

282
    // RETURN WITH THE REQUEST AND PAYLOAD
283
    return {
1✔
284
      method: 'POST',
285
      url: `https://${host}/openrtb/?sid=${sourceId}`,
286
      data: {
287
        id: bidderRequest.bidderRequestId,
288
        imp: imps,
289
        site: site,
290
        device: device
291
      },
292
      options: {
293
        withCredentials: true
294
      }
295
    };
296
  },
297

298
  // INITIATE USER SYNCING
299
  getUserSyncs: function(options, rtbResponse, gdprConsent) {
300
    const syncs = [];
1✔
301
    const bidResponse = rtbResponse?.[0]?.body ?? null;
1!
302
    const scope = this;
1✔
303

304
    // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC
305
    window.addEventListener('message', function (event) {
1✔
UNCOV
306
      if (event.data.sentinel && event.data.sentinel === 'dblks_syncData') {
×
307
        // STORE FOUND SYNCS
UNCOV
308
        if (event.data.syncs) {
×
UNCOV
309
          scope.store_syncs(event.data.syncs);
×
310
        }
311
      }
312
    });
313

314
    // POPULATE GDPR INFORMATION
315
    const gdprData = {
1✔
316
      gdpr: 0,
317
      gdprConsent: ''
318
    }
319
    if (typeof gdprConsent === 'object') {
1✔
320
      if (typeof gdprConsent.gdprApplies === 'boolean') {
1!
321
        gdprData.gdpr = Number(gdprConsent.gdprApplies);
1✔
322
        gdprData.gdprConsent = gdprConsent.consentString;
1✔
323
      } else {
324
        gdprData.gdprConsent = gdprConsent.consentString;
×
325
      }
326
    }
327

328
    // EXTRACT BUYERID COOKIE VALUE FROM BID RESPONSE AND PUT INTO STORAGE
329
    let dbBuyerId = this.get_dbid() || '';
1✔
330
    if (bidResponse.ext && bidResponse.ext.buyerid) {
1✔
331
      dbBuyerId = bidResponse.ext.buyerid;
1✔
332
      this.store_dbid(dbBuyerId);
1✔
333
    }
334

335
    // EXTRACT USERSYNCS FROM BID RESPONSE
336
    if (bidResponse.ext && bidResponse.ext.syncs) {
1✔
337
      bidResponse.ext.syncs.forEach(sync => {
1✔
338
        if (checkValid(sync)) {
2✔
339
          syncs.push(addParams(sync));
2✔
340
        }
341
      })
342
    }
343

344
    // APPEND PARAMS TO SYNC URL
345
    function addParams(sync) {
346
      // PARSE THE URL
347
      try {
2✔
348
        const url = new URL(sync.url);
2✔
349
        const urlParams = {};
2✔
350
        for (const [key, value] of url.searchParams.entries()) {
2✔
UNCOV
351
          urlParams[key] = value;
×
352
        };
353

354
        // APPLY EXTRA VARS
355
        urlParams.gdpr = gdprData.gdpr;
2✔
356
        urlParams.gdprConsent = gdprData.gdprConsent;
2✔
357
        urlParams.bidid = bidResponse.bidid;
2✔
358
        urlParams.id = bidResponse.id;
2✔
359
        urlParams.uid = dbBuyerId;
2✔
360

361
        // REBUILD URL
362
        sync.url = `${url.origin}${url.pathname}?${Object.keys(urlParams).map(key => key + '=' + encodeURIComponent(urlParams[key])).join('&')}`;
10✔
363
      } catch (e) {};
364

365
      // RETURN THE REBUILT URL
366
      return sync;
2✔
367
    }
368

369
    // ENSURE THAT THE SYNC TYPE IS VALID AND HAS PERMISSION
370
    function checkValid(sync) {
371
      if (!sync.type || !sync.url) {
2!
UNCOV
372
        return false;
×
373
      }
374
      switch (sync.type) {
2!
375
        case 'iframe':
376
          return options.iframeEnabled;
1✔
377
        case 'image':
378
          return options.pixelEnabled;
1✔
379
        default:
UNCOV
380
          return false;
×
381
      }
382
    }
383
    return syncs;
1✔
384
  },
385

386
  // DATABLOCKS WON THE AUCTION - REPORT SUCCESS
387
  onBidWon: function(bid) {
388
    this.queue_metric({type: 'bid_won', source_id: bid.params[0].source_id, req_id: bid.requestId, slot_id: bid.adUnitCode, auction_id: bid.auctionId, size: bid.size, cpm: bid.cpm, pb: bid.adserverTargeting.hb_pb, rt: bid.timeToRespond, ttl: bid.ttl});
1✔
389
  },
390

391
  // TARGETING HAS BEEN SET
392
  onSetTargeting: function(bid) {
393
    // LISTEN FOR VIEWABILITY EVENTS
394
    this.get_viewability(bid);
1✔
395
  },
396

397
  // PARSE THE RTB RESPONSE AND RETURN FINAL RESULTS
398
  interpretResponse: function(rtbResponse, bidRequest) {
399
    const bids = [];
1✔
400
    const resBids = deepAccess(rtbResponse, 'body.seatbid') || [];
1!
401
    resBids.forEach(bid => {
1✔
402
      const resultItem = {requestId: bid.id, cpm: bid.price, creativeId: bid.crid, currency: bid.currency || 'USD', netRevenue: true, ttl: bid.ttl || 360, meta: {advertiserDomains: bid.adomain}};
3✔
403

404
      const mediaType = deepAccess(bid, 'ext.mtype') || '';
3!
405
      switch (mediaType) {
3!
406
        case 'banner':
407
          bids.push(Object.assign({}, resultItem, {mediaType: BANNER, width: bid.w, height: bid.h, ad: bid.adm}));
2✔
408
          break;
2✔
409

410
        case 'native':
411
          const nativeResult = JSON.parse(bid.adm);
1✔
412
          bids.push(Object.assign({}, resultItem, {mediaType: NATIVE, native: parseNativeResponse(nativeResult.native)}));
1✔
413
          break;
1✔
414

415
        default:
UNCOV
416
          break;
×
417
      }
418
    })
419

420
    return bids;
1✔
421
  }
422
};
423

424
// DETECT BOTS
425
export class BotClientTests {
426
  constructor() {
427
    this.tests = {
3✔
428
      headless_chrome: function() {
429
        // Warning: accessing navigator.webdriver may impact fingerprinting scores when this API is included in the built script.
430
        return isWebdriverEnabled();
3✔
431
      },
432

433
      selenium: function () {
434
        let response = false;
3✔
435

436
        if (window && document) {
3✔
437
          const results = [
3✔
438
            'webdriver' in window,
439
            '_Selenium_IDE_Recorder' in window,
440
            'callSelenium' in window,
441
            '_selenium' in window,
442
            '__webdriver_script_fn' in document,
443
            '__driver_evaluate' in document,
444
            '__webdriver_evaluate' in document,
445
            '__selenium_evaluate' in document,
446
            '__fxdriver_evaluate' in document,
447
            '__driver_unwrapped' in document,
448
            '__webdriver_unwrapped' in document,
449
            '__selenium_unwrapped' in document,
450
            '__fxdriver_unwrapped' in document,
451
            '__webdriver_script_func' in document,
452
            document.documentElement.getAttribute('selenium') !== null,
453
            document.documentElement.getAttribute('webdriver') !== null,
454
            document.documentElement.getAttribute('driver') !== null
455
          ];
456

457
          results.forEach(result => {
3✔
458
            if (result === true) {
51!
UNCOV
459
              response = true;
×
460
            }
461
          })
462
        }
463

464
        return response;
3✔
465
      },
466
    }
467
  }
468
  doTests() {
469
    let response = false;
3✔
470
    for (const i of Object.keys(this.tests)) {
3✔
471
      if (this.tests[i]() === true) {
6✔
472
        response = true;
3✔
473
      }
474
    }
475
    return response;
3✔
476
  }
477
}
478

479
// INIT OUR BIDDER WITH PREBID
480
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