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

prebid / Prebid.js / 16197995703

10 Jul 2025 02:32PM UTC coverage: 96.232% (+0.003%) from 96.229%
16197995703

push

github

web-flow
Linting: remove exception (#13518)

* bump coveralls

* remove exceptions

* results

* eslint fix

* Update package-lock.json

* Update package-lock.json

* Core: remove codex comments and unused lint rule (#13520)

---------

Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>

39174 of 48116 branches covered (81.42%)

9842 of 9975 new or added lines in 861 files covered. (98.67%)

15 existing lines in 8 files now uncovered.

192483 of 200020 relevant lines covered (96.23%)

88.4 hits per line

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

89.04
/modules/roxotAnalyticsAdapter.js
1
import {deepClone, getParameterByName, logError, logInfo} from '../src/utils.js';
1✔
2
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
3
import { EVENTS } from '../src/constants.js';
4
import adapterManager from '../src/adapterManager.js';
5

6
import {ajaxBuilder} from '../src/ajax.js';
7
import {getStorageManager} from '../src/storageManager.js';
8
import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js';
9

10
const MODULE_CODE = 'roxot';
1✔
11

12
const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE});
1✔
13

14
const ajax = ajaxBuilder(0);
1✔
15

16
const DEFAULT_EVENT_URL = 'pa.rxthdr.com/v3';
1✔
17
const DEFAULT_SERVER_CONFIG_URL = 'pa.rxthdr.com/v3';
1✔
18
const analyticsType = 'endpoint';
1✔
19

20
const {
21
  AUCTION_INIT,
22
  AUCTION_END,
23
  BID_REQUESTED,
24
  BID_ADJUSTMENT,
25
  BIDDER_DONE,
26
  BID_WON
27
} = EVENTS;
1✔
28

29
const AUCTION_STATUS = {
1✔
30
  'RUNNING': 'running',
31
  'FINISHED': 'finished'
32
};
33
const BIDDER_STATUS = {
1✔
34
  'REQUESTED': 'requested',
35
  'BID': 'bid',
36
  'NO_BID': 'noBid',
37
  'TIMEOUT': 'timeout'
38
};
39
const ROXOT_EVENTS = {
1✔
40
  'AUCTION': 'a',
41
  'IMPRESSION': 'i',
42
  'BID_AFTER_TIMEOUT': 'bat'
43
};
44

45
let initOptions = {};
1✔
46

47
const localStoragePrefix = 'roxot_analytics_';
1✔
48

49
const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
1✔
50
const utmTtlKey = 'utm_ttl';
1✔
51
const utmTtl = 60 * 60 * 1000;
1✔
52

53
const isNewKey = 'is_new_flag';
1✔
54
const isNewTtl = 60 * 60 * 1000;
1✔
55

56
const auctionCache = {};
1✔
57
const auctionTtl = 60 * 60 * 1000;
1✔
58

59
const sendEventCache = [];
1✔
60
let sendEventTimeoutId = null;
1✔
61
const sendEventTimeoutTime = 1000;
1✔
62

63
function detectDevice() {
64
  if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) {
10!
65
    return 'tablet';
×
66
  }
67
  if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) {
10!
68
    return 'mobile';
×
69
  }
70
  return 'desktop';
10✔
71
}
72

73
function checkIsNewFlag() {
74
  const key = buildLocalStorageKey(isNewKey);
1✔
75
  const lastUpdate = Number(storage.getDataFromLocalStorage(key));
1✔
76
  storage.setDataInLocalStorage(key, Date.now());
1✔
77
  return Date.now() - lastUpdate > isNewTtl;
1✔
78
}
79

80
function updateUtmTimeout() {
81
  storage.setDataInLocalStorage(buildLocalStorageKey(utmTtlKey), Date.now());
5✔
82
}
83

84
function isUtmTimeoutExpired() {
85
  const utmTimestamp = storage.getDataFromLocalStorage(buildLocalStorageKey(utmTtlKey));
55✔
86
  return (Date.now() - utmTimestamp) > utmTtl;
55✔
87
}
88

89
function buildLocalStorageKey(key) {
90
  return localStoragePrefix.concat(key);
68✔
91
}
92

93
function isSupportedAdUnit(adUnit) {
94
  if (!initOptions.adUnits.length) {
20✔
95
    return true;
10✔
96
  }
97

98
  return initOptions.adUnits.includes(adUnit);
10✔
99
}
100

101
function deleteOldAuctions() {
102
  for (const auctionId in auctionCache) {
2✔
103
    const auction = auctionCache[auctionId];
2✔
104
    if (Date.now() - auction.start > auctionTtl) {
2!
105
      delete auctionCache[auctionId];
×
106
    }
107
  }
108
}
109

110
function buildAuctionEntity(args) {
111
  return {
2✔
112
    'id': args.auctionId,
113
    'start': args.timestamp,
114
    'timeout': args.timeout,
115
    'adUnits': {}
116
  };
117
}
118

119
function extractAdUnitCode(args) {
120
  return args.adUnitCode.toLowerCase();
28✔
121
}
122

123
function extractBidder(args) {
124
  return args.bidder.toLowerCase();
28✔
125
}
126

127
function buildAdUnitAuctionEntity(auction, bidRequest) {
128
  return {
5✔
129
    'adUnit': extractAdUnitCode(bidRequest),
130
    'start': auction.start,
131
    'timeout': auction.timeout,
132
    'finish': 0,
133
    'status': AUCTION_STATUS.RUNNING,
134
    'bidders': {}
135
  };
136
}
137

138
function buildBidderRequest(auction, bidRequest) {
139
  return {
5✔
140
    'bidder': extractBidder(bidRequest),
141
    'isAfterTimeout': auction.status === AUCTION_STATUS.FINISHED ? 1 : 0,
5!
142
    'start': bidRequest.startTime || Date.now(),
5!
143
    'finish': 0,
144
    'status': BIDDER_STATUS.REQUESTED,
145
    'cpm': -1,
146
    'size': {
147
      'width': 0,
148
      'height': 0
149
    },
150
    'mediaType': '-',
151
    'source': bidRequest.source || 'client'
10✔
152
  };
153
}
154

155
function buildBidAfterTimeout(adUnitAuction, args) {
156
  return {
2✔
157
    'auction': deepClone(adUnitAuction),
158
    'adUnit': extractAdUnitCode(args),
159
    'bidder': extractBidder(args),
160
    'cpm': args.cpm,
161
    'size': {
162
      'width': args.width || 0,
2!
163
      'height': args.height || 0
2!
164
    },
165
    'mediaType': args.mediaType || '-',
2!
166
    'start': args.requestTimestamp,
167
    'finish': args.responseTimestamp,
168
  };
169
}
170

171
function buildImpression(adUnitAuction, args) {
172
  return {
1✔
173
    'isNew': checkIsNewFlag() ? 1 : 0,
1!
174
    'auction': deepClone(adUnitAuction),
175
    'adUnit': extractAdUnitCode(args),
176
    'bidder': extractBidder(args),
177
    'cpm': args.cpm,
178
    'size': {
179
      'width': args.width,
180
      'height': args.height
181
    },
182
    'mediaType': args.mediaType,
183
    'source': args.source || 'client'
1!
184
  };
185
}
186

187
function handleAuctionInit(args) {
188
  auctionCache[args.auctionId] = buildAuctionEntity(args);
2✔
189
  deleteOldAuctions();
2✔
190
}
191

192
function handleBidRequested(args) {
193
  const auction = auctionCache[args.auctionId];
2✔
194
  args.bids.forEach(function (bidRequest) {
2✔
195
    const adUnitCode = extractAdUnitCode(bidRequest);
6✔
196
    const bidder = extractBidder(bidRequest);
6✔
197
    if (!isSupportedAdUnit(adUnitCode)) {
6✔
198
      return;
1✔
199
    }
200
    auction['adUnits'][adUnitCode] = auction['adUnits'][adUnitCode] || buildAdUnitAuctionEntity(auction, bidRequest);
5✔
201
    const adUnitAuction = auction['adUnits'][adUnitCode];
5✔
202
    adUnitAuction['bidders'][bidder] = adUnitAuction['bidders'][bidder] || buildBidderRequest(auction, bidRequest);
5✔
203
  });
204
}
205

206
function handleBidAdjustment(args) {
207
  const adUnitCode = extractAdUnitCode(args);
6✔
208
  const bidder = extractBidder(args);
6✔
209
  if (!isSupportedAdUnit(adUnitCode)) {
6✔
210
    return;
1✔
211
  }
212

213
  const adUnitAuction = auctionCache[args.auctionId]['adUnits'][adUnitCode];
5✔
214
  if (adUnitAuction.status === AUCTION_STATUS.FINISHED) {
5✔
215
    handleBidAfterTimeout(adUnitAuction, args);
2✔
216
    return;
2✔
217
  }
218

219
  const bidderRequest = adUnitAuction['bidders'][bidder];
3✔
220
  if (bidderRequest.cpm < args.cpm) {
3✔
221
    bidderRequest.cpm = args.cpm;
3✔
222
    bidderRequest.finish = args.responseTimestamp;
3✔
223
    bidderRequest.status = args.cpm === 0 ? BIDDER_STATUS.NO_BID : BIDDER_STATUS.BID;
3✔
224
    bidderRequest.size.width = args.width || 0;
3✔
225
    bidderRequest.size.height = args.height || 0;
3✔
226
    bidderRequest.mediaType = args.mediaType || '-';
3!
227
    bidderRequest.source = args.source || 'client';
3!
228
  }
229
}
230

231
function handleBidAfterTimeout(adUnitAuction, args) {
232
  const bidder = extractBidder(args);
2✔
233
  const bidderRequest = adUnitAuction['bidders'][bidder];
2✔
234
  const bidAfterTimeout = buildBidAfterTimeout(adUnitAuction, args);
2✔
235

236
  if (bidAfterTimeout.cpm > bidderRequest.cpm) {
2✔
237
    bidderRequest.cpm = bidAfterTimeout.cpm;
2✔
238
    bidderRequest.isAfterTimeout = 1;
2✔
239
    bidderRequest.finish = bidAfterTimeout.finish;
2✔
240
    bidderRequest.size = bidAfterTimeout.size;
2✔
241
    bidderRequest.mediaType = bidAfterTimeout.mediaType;
2✔
242
    bidderRequest.status = bidAfterTimeout.cpm === 0 ? BIDDER_STATUS.NO_BID : BIDDER_STATUS.BID;
2!
243
  }
244

245
  registerEvent(ROXOT_EVENTS.BID_AFTER_TIMEOUT, 'Bid After Timeout', bidAfterTimeout);
2✔
246
}
247

248
function handleBidderDone(args) {
249
  const auction = auctionCache[args.auctionId];
2✔
250

251
  args.bids.forEach(function (bidDone) {
2✔
252
    const adUnitCode = extractAdUnitCode(bidDone);
6✔
253
    const bidder = extractBidder(bidDone);
6✔
254
    if (!isSupportedAdUnit(adUnitCode)) {
6✔
255
      return;
1✔
256
    }
257

258
    const adUnitAuction = auction['adUnits'][adUnitCode];
5✔
259
    if (adUnitAuction.status === AUCTION_STATUS.FINISHED) {
5✔
260
      return;
5✔
261
    }
NEW
262
    const bidderRequest = adUnitAuction['bidders'][bidder];
×
263
    if (bidderRequest.status !== BIDDER_STATUS.REQUESTED) {
×
264
      return;
×
265
    }
266

267
    bidderRequest.finish = Date.now();
×
268
    bidderRequest.status = BIDDER_STATUS.NO_BID;
×
269
    bidderRequest.cpm = 0;
×
270
  });
271
}
272

273
function handleAuctionEnd(args) {
274
  const auction = auctionCache[args.auctionId];
2✔
275
  if (!Object.keys(auction.adUnits).length) {
2!
276
    delete auctionCache[args.auctionId];
×
277
  }
278

279
  const finish = Date.now();
2✔
280
  auction.finish = finish;
2✔
281
  for (const adUnit in auction.adUnits) {
2✔
282
    const adUnitAuction = auction.adUnits[adUnit];
5✔
283
    adUnitAuction.finish = finish;
5✔
284
    adUnitAuction.status = AUCTION_STATUS.FINISHED;
5✔
285

286
    for (const bidder in adUnitAuction.bidders) {
5✔
287
      const bidderRequest = adUnitAuction.bidders[bidder];
5✔
288
      if (bidderRequest.status !== BIDDER_STATUS.REQUESTED) {
5✔
289
        continue;
3✔
290
      }
291

292
      bidderRequest.status = BIDDER_STATUS.TIMEOUT;
2✔
293
    }
294
  }
295

296
  registerEvent(ROXOT_EVENTS.AUCTION, 'Auction', auction);
2✔
297
}
298

299
function handleBidWon(args) {
300
  const adUnitCode = extractAdUnitCode(args);
2✔
301
  if (!isSupportedAdUnit(adUnitCode)) {
2✔
302
    return;
1✔
303
  }
304
  const adUnitAuction = auctionCache[args.auctionId]['adUnits'][adUnitCode];
1✔
305
  const impression = buildImpression(adUnitAuction, args);
1✔
306
  registerEvent(ROXOT_EVENTS.IMPRESSION, 'Bid won', impression);
1✔
307
}
308

309
function handleOtherEvents(eventType, args) {
310
  registerEvent(eventType, eventType, args);
8✔
311
}
312

313
const roxotAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), {
1✔
314
  track({eventType, args}) {
24✔
315
    switch (eventType) {
24✔
316
      case AUCTION_INIT:
317
        handleAuctionInit(args);
2✔
318
        break;
2✔
319
      case BID_REQUESTED:
320
        handleBidRequested(args);
2✔
321
        break;
2✔
322
      case BID_ADJUSTMENT:
323
        handleBidAdjustment(args);
6✔
324
        break;
6✔
325
      case BIDDER_DONE:
326
        handleBidderDone(args);
2✔
327
        break;
2✔
328
      case AUCTION_END:
329
        handleAuctionEnd(args);
2✔
330
        break;
2✔
331
      case BID_WON:
332
        handleBidWon(args);
2✔
333
        break;
2✔
334
      default:
335
        handleOtherEvents(eventType, args);
8✔
336
        break;
8✔
337
    }
338
  },
339

340
});
341

342
roxotAdapter.originEnableAnalytics = roxotAdapter.enableAnalytics;
1✔
343

344
roxotAdapter.enableAnalytics = function (config) {
1✔
345
  if (this.initConfig(config)) {
10✔
346
    _logInfo('Analytics adapter enabled', initOptions);
10✔
347
    roxotAdapter.originEnableAnalytics(config);
10✔
348
  }
349
};
350

351
roxotAdapter.buildUtmTagData = function () {
1✔
352
  const utmTagData = {};
11✔
353
  let utmTagsDetected = false;
11✔
354
  utmTags.forEach(function (utmTagKey) {
11✔
355
    const utmTagValue = getParameterByName(utmTagKey);
55✔
356
    if (utmTagValue !== '') {
55!
357
      utmTagsDetected = true;
×
358
    }
359
    utmTagData[utmTagKey] = utmTagValue;
55✔
360
  });
361
  utmTags.forEach(function (utmTagKey) {
11✔
362
    if (utmTagsDetected) {
55!
363
      storage.setDataInLocalStorage(buildLocalStorageKey(utmTagKey), utmTagData[utmTagKey]);
×
364
      updateUtmTimeout();
×
365
    } else {
366
      if (!isUtmTimeoutExpired()) {
55✔
367
        utmTagData[utmTagKey] = storage.getDataFromLocalStorage(buildLocalStorageKey(utmTagKey)) ? storage.getDataFromLocalStorage(buildLocalStorageKey(utmTagKey)) : '';
5✔
368
        updateUtmTimeout();
5✔
369
      }
370
    }
371
  });
372
  return utmTagData;
11✔
373
};
374

375
roxotAdapter.initConfig = function (config) {
1✔
376
  let isCorrectConfig = true;
10✔
377
  initOptions = {};
10✔
378
  initOptions.options = deepClone(config.options);
10✔
379

380
  initOptions.publisherId = initOptions.options.publisherId || (initOptions.options.publisherIds[0]) || null;
10!
381
  if (!initOptions.publisherId) {
10!
382
    _logError('"options.publisherId" is empty');
×
383
    isCorrectConfig = false;
×
384
  }
385

386
  initOptions.adUnits = initOptions.options.adUnits || [];
10✔
387
  initOptions.adUnits = initOptions.adUnits.map(value => value.toLowerCase());
10✔
388
  initOptions.server = initOptions.options.server || DEFAULT_EVENT_URL;
10✔
389
  initOptions.configServer = initOptions.options.configServer || (initOptions.options.server || DEFAULT_SERVER_CONFIG_URL);
10✔
390
  initOptions.utmTagData = this.buildUtmTagData();
10✔
391
  initOptions.host = initOptions.options.host || window.location.hostname;
10✔
392
  initOptions.device = detectDevice();
10✔
393

394
  loadServerConfig();
10✔
395
  return isCorrectConfig;
10✔
396
};
397

398
roxotAdapter.getOptions = function () {
1✔
399
  return initOptions;
13✔
400
};
401

402
function registerEvent(eventType, eventName, data) {
403
  const eventData = {
13✔
404
    eventType: eventType,
405
    eventName: eventName,
406
    data: data
407
  };
408

409
  sendEventCache.push(eventData);
13✔
410

411
  _logInfo('Register event', eventData);
13✔
412

413
  (typeof initOptions.serverConfig === 'undefined') ? checkEventAfterTimeout() : checkSendEvent();
13!
414
}
415

416
function checkSendEvent() {
417
  if (sendEventTimeoutId) {
13!
418
    clearTimeout(sendEventTimeoutId);
×
419
    sendEventTimeoutId = null;
×
420
  }
421

422
  if (typeof initOptions.serverConfig === 'undefined') {
13!
423
    checkEventAfterTimeout();
×
424
    return;
×
425
  }
426

427
  while (sendEventCache.length) {
13✔
428
    const event = sendEventCache.shift();
13✔
429
    const isNeedSend = initOptions.serverConfig[event.eventType] || 0;
13✔
430
    if (Number(isNeedSend) === 0) {
13✔
431
      _logInfo('Skip event ' + event.eventName, event);
8✔
432
      continue;
8✔
433
    }
434
    sendEvent(event.eventType, event.eventName, event.data);
5✔
435
  }
436
}
437

438
function checkEventAfterTimeout() {
439
  if (sendEventTimeoutId) {
×
440
    return;
×
441
  }
442

443
  sendEventTimeoutId = setTimeout(checkSendEvent, sendEventTimeoutTime);
×
444
}
445

446
function sendEvent(eventType, eventName, data) {
447
  const url = 'https://' + initOptions.server + '/' + eventType + '?publisherId=' + initOptions.publisherId + '&host=' + initOptions.host;
5✔
448
  const eventData = {
5✔
449
    'event': eventType,
450
    'eventName': eventName,
451
    'options': initOptions,
452
    'data': data
453
  };
454

455
  ajax(
5✔
456
    url,
457
    function () {
458
      _logInfo(eventName + ' sent', eventData);
×
459
    },
460
    JSON.stringify(eventData),
461
    {
462
      contentType: 'text/plain',
463
      method: 'POST',
464
      withCredentials: true
465
    }
466
  );
467
}
468

469
function loadServerConfig() {
470
  const url = 'https://' + initOptions.configServer + '/c' + '?publisherId=' + initOptions.publisherId + '&host=' + initOptions.host;
10✔
471
  ajax(
10✔
472
    url,
473
    {
474
      'success': function (data) {
475
        initOptions.serverConfig = JSON.parse(data);
2✔
476
      },
477
      'error': function () {
478
        initOptions.serverConfig = {};
1✔
479
        initOptions.serverConfig[ROXOT_EVENTS.AUCTION] = 1;
1✔
480
        initOptions.serverConfig[ROXOT_EVENTS.IMPRESSION] = 1;
1✔
481
        initOptions.serverConfig[ROXOT_EVENTS.BID_AFTER_TIMEOUT] = 1;
1✔
482
        initOptions.serverConfig['isError'] = 1;
1✔
483
      }
484
    },
485
    null,
486
    {
487
      contentType: 'text/json',
488
      method: 'GET',
489
      withCredentials: true
490
    }
491
  );
492
}
493

494
function _logInfo(message, meta) {
495
  logInfo(buildLogMessage(message), meta);
31✔
496
}
497

498
function _logError(message) {
499
  logError(buildLogMessage(message));
×
500
}
501

502
function buildLogMessage(message) {
503
  return 'Roxot Prebid Analytics: ' + message;
31✔
504
}
505

506
adapterManager.registerAnalyticsAdapter({
1✔
507
  adapter: roxotAdapter,
508
  code: MODULE_CODE,
509
});
510

511
export default roxotAdapter;
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