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

prebid / Prebid.js / 16885668971

11 Aug 2025 04:12PM UTC coverage: 96.248% (-0.006%) from 96.254%
16885668971

push

github

46b4fe
web-flow
PubMatic Analytics Adapter Optimization. (#13711)

* Targetting key set for floor applied from PM RTD module

* Test Cases Added

* UPR related changes

* Minor changes

* Added targeting keys in constants

* UOE-12412: Added floorProvider = "PM" related check to set the targeting

* UOE-12412: Removed modelVersion related check

* UOE-12412: Changed Key Name for targeting

* UOE-12412: Enabling and disabling targetting key based on adServertargeting coming from config

* UOE-12412: RTD provider error handling for undefined configs

* Refactor: Improve bid status handling and floor value detection for No Bids scenario in PubMatic RTD provider

* Refactor: Extract bid targeting logic into separate functions

* Refactor: Improve pubmatic RTD provider targeting logic and add test coverage

* Enhance PubMatic RTD floor calculation with multi-size support and targeting precision

* UOE-12413: Changed adServerTargeting to pmTargetingKeys

* Enhance multiplier handling in pubmatic RTD provider

* PubM RTD Module: Update pubmatic RTD provider with enhanced targeting logic and test coverage

* PubM RTD Module: Multipliers fallback mechanism implemented and test cases edited

* Code changes optimisation

* Test case optimized

* Test cases: add unit tests for multiplier extraction in pubmatic RTD provider

* refactor: reorder multiplier sources in pubmaticRtdProvider to prioritize config.json over floor.json

* Fix: update NOBID multiplier from 1.6 to 1.2 in pubmaticRtdProvider module

* Refactor: enhance floor value calculation for multi-format ad units and improve logging

* Refactor: Add getBidder function and remove unused findWinningBid import in PubMatic RTD provider tests

* chore: remove unused pubmaticRtd example and noconfig files

* PubMatic RTD module markdown file update having targetingKey details

* Fix:  Removed extra whitespace and normalize line endings in RTD provider

* fix: add colon to RTD targeting log message in pubmaticRtdProvider

* Pu... (continued)

39409 of 48455 branches covered (81.33%)

603 of 626 new or added lines in 2 files covered. (96.33%)

12 existing lines in 9 files now uncovered.

195381 of 202997 relevant lines covered (96.25%)

123.94 hits per line

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

87.45
/modules/pubmaticAnalyticsAdapter.js
1
import { isArray, logError, logWarn, pick } from '../src/utils.js';
1✔
2
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
3
import adapterManager from '../src/adapterManager.js';
4
import { BID_STATUS, STATUS, REJECTION_REASON } from '../src/constants.js';
5
import { ajax } from '../src/ajax.js';
6
import { config } from '../src/config.js';
7
import { getGlobal } from '../src/prebidGlobal.js';
8
import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js';
9

10
/// /////////// CONSTANTS ///////////////
11
const ADAPTER_CODE = 'pubmatic';
1✔
12

13
const DISPLAY_MANAGER = 'Prebid.js';
1✔
14
const VENDOR_OPENWRAP = 'openwrap';
1✔
15
const SEND_TIMEOUT = 2000;
1✔
16
const END_POINT_HOST = 'https://t.pubmatic.com/';
1✔
17
const END_POINT_VERSION = 1;
1✔
18
const PAGE_SOURCE = 'web';
1✔
19
const END_POINT_BID_LOGGER = END_POINT_HOST + 'wl?';
1✔
20
const END_POINT_WIN_BID_LOGGER = END_POINT_HOST + 'wt?';
1✔
21
const LOG_PRE_FIX = 'PubMatic-Analytics: ';
1✔
22
const cache = {
1✔
23
  auctions: {}
24
};
25
const SUCCESS = 'success';
1✔
26
const NO_BID = 'no-bid';
1✔
27
const ERROR = 'error';
1✔
28
const REQUEST_ERROR = 'request-error';
1✔
29
const TIMEOUT_ERROR = 'timeout-error';
1✔
30
const CURRENCY_USD = 'USD';
1✔
31
const BID_PRECISION = 2;
1✔
32
const EMPTY_STRING = '';
1✔
33
// todo: input profileId and profileVersionId ; defaults to zero or one
34
const DEFAULT_PUBLISHER_ID = 0;
1✔
35
const DEFAULT_PROFILE_ID = 0;
1✔
36
const DEFAULT_PROFILE_VERSION_ID = 0;
1✔
37

38
/// /////////// VARIABLES //////////////
39
let publisherId = DEFAULT_PUBLISHER_ID; // int: mandatory
1✔
40
let profileId = DEFAULT_PROFILE_ID; // int: optional
1✔
41
let profileVersionId = DEFAULT_PROFILE_VERSION_ID; // int: optional
1✔
42
let s2sBidders = [];
1✔
43
let _country = '';
1✔
44

45
/// /////////// HELPER FUNCTIONS //////////////
46

47
function formatSource(src = 'client') {
31!
48
  return (src === 's2s' ? 'server' : src).toLowerCase();
31✔
49
}
50

51
/**
52
 * Validates payload and sends API request with proper error handling
53
 * @param {Object} options - Configuration options
54
 * @param {Object} options.payload - The payload to send
55
 * @param {string} options.endpoint - API endpoint to use
56
 * @param {string} options.loggerType - Type of logger for error messages
57
 * @returns {boolean} - True if request was sent, false if validation failed
58
 */
59
function validateAndSendRequest(options) {
60
  const { payload, endpoint, loggerType } = options;
43✔
61

62
  // Check for critical payload data
63
  if (!Object.keys(payload?.rd || {}).length || !Object.keys(payload?.sd || {}).length) {
43!
NEW
64
    logWarn(LOG_PRE_FIX + `Empty or invalid payload for ${loggerType}, suppressing API call`);
×
NEW
65
    return false;
×
66
  }
67

68
  const urlParams = new URLSearchParams(new URL(payload.rd.purl).search);
43✔
69
  const queryParams = `v=${END_POINT_VERSION}&psrc=${PAGE_SOURCE}${urlParams.get('pmad') === '1' ? '&debug=1' : ''}`;
43!
70

71
  try {
43✔
72
    sendAjaxRequest({
43✔
73
      endpoint,
74
      method: 'POST',
75
      queryParams,
76
      body: JSON.stringify(payload)
77
    });
78
    return true;
43✔
79
  } catch (e) {
NEW
80
    logError(LOG_PRE_FIX + `Error stringifying payload for ${loggerType}:`, e);
×
NEW
81
    return false;
×
82
  }
83
}
84

85
function sendAjaxRequest({ endpoint, method, queryParams = '', body = null }) {
43!
86
  // Return early if body is null or undefined
87
  if (body === null || body === undefined) {
43!
NEW
88
    logWarn(LOG_PRE_FIX + 'Empty body in sendAjaxRequest, suppressing API call');
×
NEW
89
    return;
×
90
  }
91

92
  const url = queryParams ? `${endpoint}${queryParams}` : endpoint;
43!
93
  return ajax(url, null, body, { method });
43✔
94
};
95

96
function copyRequiredBidDetails(bid) {
97
  return pick(bid, [
35✔
98
    'bidder',
99
    'bidderCode',
100
    'adapterCode',
101
    'bidId',
102
    'adUnitId', () => bid.adUnitCode,
35✔
103
    'status', () => NO_BID, // default a bid to NO_BID until response is received or bid is timed out
35✔
104
    'finalSource as source',
105
    'params',
106
    'adUnit', () => pick(bid, [
35✔
107
      'adUnitCode',
108
      'transactionId',
109
      'sizes as dimensions',
110
      'mediaTypes'
111
    ])
112
  ]);
113
}
114

115
function setBidStatus(bid, status) {
116
  switch (status) {
31!
117
    case STATUS.GOOD:
118
      bid.status = SUCCESS;
31✔
119
      delete bid.error; // it's possible for this to be set by a previous timeout
31✔
120
      break;
31✔
121
    default:
122
      bid.status = ERROR;
×
123
      bid.error = {
×
124
        code: REQUEST_ERROR
125
      };
126
  }
127
}
128

129
function parseBidResponse(bid) {
130
  return pick(bid, [
31✔
131
    'bidPriceUSD', () => {
132
      // todo: check whether currency cases are handled here
133
      if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === CURRENCY_USD) {
31✔
134
        return window.parseFloat(Number(bid.cpm).toFixed(BID_PRECISION));
28✔
135
      }
136
      // use currency conversion function if present
137
      if (typeof bid.getCpmInNewCurrency === 'function') {
3!
138
        return window.parseFloat(Number(bid.getCpmInNewCurrency(CURRENCY_USD)).toFixed(BID_PRECISION));
×
139
      }
140
      logWarn(LOG_PRE_FIX + 'Could not determine the Net cpm in USD for the bid thus using bid.cpm', bid);
3✔
141
      return bid.cpm
3✔
142
    },
143
    'bidGrossCpmUSD', () => {
144
      if (typeof bid.originalCurrency === 'string' && bid.originalCurrency.toUpperCase() === CURRENCY_USD) {
31✔
145
        return window.parseFloat(Number(bid.originalCpm).toFixed(BID_PRECISION));
28✔
146
      }
147
      // use currency conversion function if present
148
      if (typeof getGlobal().convertCurrency === 'function') {
3✔
149
        return window.parseFloat(Number(getGlobal().convertCurrency(bid.originalCpm, bid.originalCurrency, CURRENCY_USD)).toFixed(BID_PRECISION));
3✔
150
      }
151
      logWarn(LOG_PRE_FIX + 'Could not determine the Gross cpm in USD for the bid, thus using bid.originalCpm', bid);
×
152
      return bid.originalCpm
×
153
    },
154
    'dealId',
155
    'currency',
156
    'cpm', () => window.parseFloat(Number(bid.cpm).toFixed(BID_PRECISION)),
31✔
157
    'originalCpm', () => window.parseFloat(Number(bid.originalCpm).toFixed(BID_PRECISION)),
31✔
158
    'originalCurrency',
159
    'adserverTargeting',
160
    'dealChannel',
161
    'meta', () => (bid.meta && Object.keys(bid.meta).length > 0 ? bid.meta : undefined),
31✔
162
    'status',
163
    'error',
164
    'bidId',
165
    'mediaType',
166
    'params',
167
    'floorData',
168
    'mi',
169
    'regexPattern', () => bid.regexPattern || undefined,
31✔
170
    'partnerImpId', // partner impression ID
171
    'dimensions', () => pick(bid, [
31✔
172
      'width',
173
      'height'
174
    ])
175
  ]);
176
}
177

178
function getAdapterNameForAlias(aliasName) {
179
  return adapterManager.aliasRegistry[aliasName] || aliasName;
123✔
180
}
181

182
function isS2SBidder(bidder) {
183
  return (s2sBidders.indexOf(bidder) > -1) ? 1 : 0
1!
184
}
185

186
function isOWPubmaticBid(adapterName) {
187
  const s2sConf = config.getConfig('s2sConfig');
26✔
188
  const s2sConfArray = s2sConf ? (isArray(s2sConf) ? s2sConf : [s2sConf]) : [];
26!
189
  return s2sConfArray.some(conf => {
26✔
190
    if (adapterName === ADAPTER_CODE && conf.defaultVendor === VENDOR_OPENWRAP &&
26✔
191
      conf.bidders.indexOf(ADAPTER_CODE) > -1) {
192
      return true;
1✔
193
    }
194
  })
195
}
196

197
function getAdUnit(adUnits, adUnitId) {
198
  return adUnits.filter(adUnit => (adUnit.divID && adUnit.divID == adUnitId) || (adUnit.code == adUnitId))[0];
60!
199
}
200

201
function getTgId() {
202
  var testGroupId = parseInt(config.getConfig('testGroupId') || 0);
43✔
203
  if (testGroupId <= 15 && testGroupId >= 0) {
43✔
204
    return testGroupId;
38✔
205
  }
206
  return 0;
5✔
207
}
208

209
function getFeatureLevelDetails(auctionCache) {
210
  const result = {};
43✔
211

212
  // Add floor data if available
213
  if (auctionCache?.floorData?.floorRequestData) {
43✔
214
    const flrData = {
41✔
215
      ...auctionCache.floorData.floorRequestData,
216
      ...(auctionCache.floorData.floorResponseData?.enforcements && { enforcements: auctionCache.floorData.floorResponseData.enforcements })
81✔
217
    };
218
    result.flr = flrData;
41✔
219
  }
220

221
  // Add bdv object with list of identity partners
222
  const identityPartners = getListOfIdentityPartners();
43✔
223
  if (identityPartners) {
43✔
224
    result.bdv = {
3✔
225
      lip: identityPartners
226
    };
227
  }
228

229
  return result;
43✔
230
}
231

232
function getListOfIdentityPartners() {
233
  const namespace = getGlobal();
43✔
234
  const publisherProvidedEids = namespace.getConfig("ortb2.user.eids") || [];
43✔
235
  const availableUserIds = namespace.getUserIds() || {};
43!
236
  const identityModules = namespace.getConfig('userSync')?.userIds || [];
43✔
237
  const identityModuleNameMap = identityModules.reduce((mapping, module) => {
43✔
238
    if (module.storage?.name) {
3✔
239
      mapping[module.storage.name] = module.name;
3✔
240
    }
241
    return mapping;
3✔
242
  }, {});
243

244
  const userIdPartners = Object.keys(availableUserIds).map(storageName =>
43✔
245
    identityModuleNameMap[storageName] || storageName
3!
246
  );
247

248
  const publisherProvidedEidList = publisherProvidedEids.map(eid =>
43✔
249
    identityModuleNameMap[eid.source] || eid.source
×
250
  );
251

252
  const identityPartners = Array.from(new Set([...userIdPartners, ...publisherProvidedEidList]));
43✔
253
  return identityPartners.length > 0 ? identityPartners : undefined;
43✔
254
}
255

256
function getRootLevelDetails(auctionCache, auctionId) {
257
  const referrer = config.getConfig('pageUrl') || auctionCache.referer || '';
43!
258
  return {
43✔
259
    pubid: `${publisherId}`,
260
    iid: `${auctionCache?.wiid || auctionId}`,
86✔
261
    to: parseInt(`${auctionCache.timeout}`),
262
    purl: referrer,
263
    tst: Math.round(Date.now() / 1000),
264
    pid: `${profileId}`,
265
    pdvid: `${profileVersionId}`,
266
    ortb2: auctionCache.ortb2,
267
    tgid: getTgId(),
268
    s2sls: s2sBidders,
269
    dm: DISPLAY_MANAGER,
270
    dmv: '$prebid.version$' || '-1'
43!
271
  }
272
}
273

274
function executeBidsLoggerCall(event, highestCpmBids) {
275
  const { auctionId } = event;
18✔
276
  const auctionCache = cache.auctions[auctionId];
18✔
277
  if (!auctionCache || auctionCache.sent) return;
18!
278
  // Fetching slotinfo at event level results to undefined so Running loop over the codes to get the GPT slot name.
279
  Object.entries(auctionCache?.adUnitCodes || {}).forEach(([adUnitCode, adUnit]) => {
35!
280
    let origAdUnit = getAdUnit(cache.auctions[auctionId]?.origAdUnits, adUnitCode) || {};
35✔
281
    auctionCache.adUnitCodes[adUnitCode].adUnitId = origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitCode)?.gptSlot || adUnitCode;
35✔
282

283
    for (let bidId in adUnit?.bids) {
35✔
284
      adUnit?.bids[bidId].forEach(bid => {
35✔
285
        bid['owAdUnitId'] = getGptSlotInfoForAdUnitCode(bid?.adUnit?.adUnitCode)?.gptSlot || bid.adUnit?.adUnitCode;
35✔
286
        const winBid = highestCpmBids.filter(cpmbid => cpmbid.adId === bid?.adId)[0]?.adId;
35✔
287
        auctionCache.adUnitCodes[bid?.adUnitId].bidWonAdId = auctionCache.adUnitCodes[bid?.adUnitId].bidWonAdId ? auctionCache.adUnitCodes[bid?.adUnitId].bidWonAdId : winBid;
35✔
288
        const prebidBidId = bid.bidResponse && bid.bidResponse.prebidBidId;
35✔
289
        bid.bidId = prebidBidId || bid.bidId || bidId;
35!
290
        bid.bidderCode = bid.bidderCode || bid.bidder;
35!
291
        let adapterName = getAdapterNameForAlias(bid.adapterCode || bid.bidder);
35!
292
        bid.adapterName = adapterName;
35✔
293
        bid.bidder = adapterName;
35✔
294
      })
295
    }
296
  });
297

298
  const payload = {
18✔
299
    sd: auctionCache.adUnitCodes,
300
    fd: getFeatureLevelDetails(auctionCache),
301
    rd: { ctr: _country || '', ...getRootLevelDetails(auctionCache, auctionId) }
35✔
302
  };
303
  auctionCache.sent = true;
18✔
304

305
  validateAndSendRequest({
18✔
306
    payload,
307
    endpoint: END_POINT_BID_LOGGER,
308
    loggerType: 'bid logger'
309
  });
310
}
311

312
function executeBidWonLoggerCall(auctionId, adUnitId) {
313
  const winningBidId = cache.auctions[auctionId]?.adUnitCodes[adUnitId]?.wonBidId;
26✔
314
  const winningBids = cache.auctions[auctionId]?.adUnitCodes[adUnitId]?.bids[winningBidId];
26✔
315
  if (!winningBids) {
26!
316
    logWarn(LOG_PRE_FIX + 'Could not find winningBids for : ', auctionId);
×
317
    return;
×
318
  }
319

320
  let winningBid = winningBids[0];
26✔
321
  if (winningBids.length > 1) {
26!
NEW
322
    winningBid = winningBids.find(bid => bid.adId === cache.auctions[auctionId]?.adUnitCodes[adUnitId]?.bidWonAdId) || winningBid;
×
323
  }
324

325
  const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder);
26!
326
  winningBid.bidId = winningBidId;
26✔
327
  if (isOWPubmaticBid(adapterName) && isS2SBidder(winningBid.bidder)) {
26✔
328
    return;
1✔
329
  }
330
  const origAdUnit = getAdUnit(cache.auctions[auctionId]?.origAdUnits, adUnitId) || {};
25✔
331
  const owAdUnitId = origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId;
25✔
332
  const auctionCache = cache.auctions[auctionId];
25✔
333
  const payload = {
25✔
334
    fd: getFeatureLevelDetails(auctionCache),
335
    rd: { ctr: _country || '', ...getRootLevelDetails(auctionCache, auctionId) },
48✔
336
    sd: {
337
      adapterName,
338
      adUnitId,
339
      ...winningBid,
340
      owAdUnitId,
341
    }
342
  };
343

344
  validateAndSendRequest({
25✔
345
    payload,
346
    endpoint: END_POINT_WIN_BID_LOGGER,
347
    loggerType: 'bid won logger'
348
  });
349
}
350

351
function readSaveCountry(e) {
352
  _country = e.bidderRequests?.length > 0
18✔
353
    ? e.bidderRequests.find(bidder => bidder?.bidderCode === ADAPTER_CODE)?.ortb2?.user?.ext?.ctr || EMPTY_STRING
1!
354
    : EMPTY_STRING;
355
}
356

357
/// /////////// ADAPTER EVENT HANDLER FUNCTIONS //////////////
358

359
const eventHandlers = {
1✔
360
  auctionInit: (args) => {
361
    s2sBidders = (function () {
20✔
362
      const result = [];
20✔
363
      try {
20✔
364
        const s2sConf = config.getConfig('s2sConfig');
20✔
365
        if (isArray(s2sConf)) {
20!
NEW
366
          s2sConf.forEach(conf => {
×
NEW
367
            if (conf?.bidders) {
×
NEW
368
              result.push(...conf.bidders);
×
369
            }
370
          });
371
        } else if (s2sConf?.bidders) {
20✔
372
          result.push(...s2sConf.bidders);
20✔
373
        }
374
      } catch (e) {
NEW
375
        logError('Error processing s2s bidders:', e);
×
376
      }
377
      return result || [];
20!
378
    }());
379
    const cacheEntry = pick(args, [
20✔
380
      'timestamp',
381
      'timeout',
382
      'bidderDonePendingCount', () => args.bidderRequests.length,
20✔
383
    ]);
384
    cacheEntry.adUnitCodes = {};
20✔
385
    cacheEntry.floorData = {};
20✔
386
    cacheEntry.origAdUnits = args.adUnits;
20✔
387
    cacheEntry.referer = args.bidderRequests[0].refererInfo.topmostLocation;
20✔
388
    cacheEntry.ortb2 = args.bidderRequests[0].ortb2
20✔
389
    cache.auctions[args.auctionId] = cacheEntry;
20✔
390
  },
391

392
  bidRequested: (args) => {
393
    args.bids.forEach(function (bid) {
18✔
394
      if (!cache.auctions[args.auctionId].adUnitCodes.hasOwnProperty(bid.adUnitCode)) {
35✔
395
        cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode] = {
35✔
396
          bids: {},
397
          wonBidId: "",
398
          dimensions: bid.sizes
399
        };
400
      }
401
      if (bid.bidder === 'pubmatic' && !!bid?.params?.wiid) {
35!
NEW
402
        cache.auctions[args.auctionId].wiid = bid.params.wiid;
×
403
      }
404
      cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId] = [copyRequiredBidDetails(bid)];
35✔
405
      if (bid.floorData) {
35✔
406
        cache.auctions[args.auctionId].floorData['floorRequestData'] = bid.floorData;
17✔
407
      }
408
    })
409
  },
410

411
  bidResponse: (args) => {
412
    if (!args.requestId) {
31!
NEW
413
      logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler');
×
NEW
414
      return;
×
415
    }
416
    const requestId = args.originalRequestId || args.requestId;
31✔
417
    let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId][0];
31✔
418
    if (!bid) {
31!
NEW
419
      logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId);
×
NEW
420
      return;
×
421
    }
422

423
    if ((bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) || (bid.bidder === args.bidderCode && bid.status === SUCCESS)) {
31!
NEW
424
      if (bid.params) {
×
NEW
425
        args.params = bid.params;
×
426
      }
NEW
427
      bid = copyRequiredBidDetails(args);
×
NEW
428
      cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId].push(bid);
×
429
    } else if (args.originalRequestId) {
31!
NEW
430
      bid.bidId = args.requestId;
×
431
    }
432

433
    if (args.floorData) {
31✔
434
      cache.auctions[args.auctionId].floorData['floorResponseData'] = args.floorData;
31✔
435
    }
436

437
    bid.adId = args.adId;
31✔
438
    bid.source = formatSource(bid.source || args.source);
31✔
439
    setBidStatus(bid, 1);
31✔
440
    const latency = args?.timeToRespond || Date.now() - cache.auctions[args.auctionId].timestamp;
31!
441
    const auctionTime = cache.auctions[args.auctionId].timeout;
31✔
442
    // Check if latency is greater than auctiontime+150, then log auctiontime+150 to avoid large numbers
443
    bid.partnerTimeToRespond = latency > (auctionTime + 150) ? (auctionTime + 150) : latency;
31!
444
    bid.clientLatencyTimeMs = Date.now() - cache.auctions[args.auctionId].timestamp;
31✔
445
    bid.bidResponse = parseBidResponse(args);
31✔
446
    bid.bidderCode = args.bidderCode || bid.bidderCode;
31✔
447
    bid.bidder = getAdapterNameForAlias(args.adapterCode || bid.bidderCode);
31✔
448
    bid.adapterName = getAdapterNameForAlias(args.adapterCode || bid.bidderCode);
31✔
449
  },
450

451
  bidRejected: (args) => {
452
    // If bid is rejected due to floors value did not met
453
    // make cpm as 0, status as bidRejected and forward the bid for logging
454
    if (args.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET) {
1✔
455
      args.cpm = 0;
1✔
456
      args.status = BID_STATUS.BID_REJECTED;
1✔
457
      eventHandlers['bidResponse'](args);
1✔
458
    }
459
  },
460

461
  bidderDone: (args) => {
462
    if (cache.auctions[args.auctionId]?.bidderDonePendingCount) {
16!
NEW
463
      cache.auctions[args.auctionId].bidderDonePendingCount--;
×
464
    }
465
    args.bids.forEach(bid => {
16✔
466
      const cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.originalRequestId || bid.requestId];
32!
467
      if (typeof bid.serverResponseTimeMs !== 'undefined') {
32✔
468
        cachedBid.serverLatencyTimeMs = bid.serverResponseTimeMs;
16✔
469
      }
470
      if (!cachedBid.status) {
32✔
471
        cachedBid.status = NO_BID;
31✔
472
      }
473
      if (!cachedBid.clientLatencyTimeMs) {
32✔
474
        cachedBid.clientLatencyTimeMs = Date.now() - cache.auctions[bid.auctionId].timestamp;
31✔
475
      }
476
    });
477
  },
478

479
  bidWon: (args) => {
480
    const auctionCache = cache.auctions[args.auctionId];
26✔
481
    auctionCache.adUnitCodes[args.adUnitCode].wonBidId = args.originalRequestId || args.requestId;
26✔
482
    auctionCache.adUnitCodes[args.adUnitCode].bidWonAdId = args.adId;
26✔
483
    executeBidWonLoggerCall(args.auctionId, args.adUnitCode);
26✔
484
  },
485

486
  auctionEnd: (args) => {
487
    // if for the given auction bidderDonePendingCount == 0 then execute logger call sooners
488
    const highestCpmBids = getGlobal().getHighestCpmBids() || [];
18!
489
    readSaveCountry(args);
18✔
490
    setTimeout(() => {
18✔
491
      executeBidsLoggerCall.call(this, args, highestCpmBids);
18✔
492
    }, (cache.auctions[args.auctionId]?.bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT));
18!
493
  },
494

495
  bidTimeout: (args) => {
496
    // db = 1 and t = 1 means bidder did NOT respond with a bid but we got a timeout notification
497
    // db = 0 and t = 1 means bidder did  respond with a bid but post timeout
498
    args.forEach(badBid => {
2✔
499
      const auctionCache = cache.auctions[badBid.auctionId];
2✔
500
      const bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[badBid.bidId || badBid.originalRequestId || badBid.requestId][0];
2!
501
      if (bid) {
2!
502
        bid.status = ERROR;
2✔
503
        bid.error = {
2✔
504
          code: TIMEOUT_ERROR
505
        };
506
      } else {
NEW
507
        logWarn(LOG_PRE_FIX + 'bid not found');
×
508
      }
509
    });
510
  }
511
}
512

513
/// /////////// ADAPTER DEFINITION //////////////
514

515
const baseAdapter = adapter({analyticsType: 'endpoint'});
1✔
516
const pubmaticAdapter = Object.assign({}, baseAdapter, {
1✔
517

518
  enableAnalytics(conf = {}) {
19!
519
    let error = false;
19✔
520

521
    if (typeof conf.options === 'object') {
19!
522
      if (conf.options.publisherId) {
19✔
523
        publisherId = Number(conf.options.publisherId);
18✔
524
      }
525
      profileId = Number(conf.options.profileId) || DEFAULT_PROFILE_ID;
19✔
526
      profileVersionId = Number(conf.options.profileVersionId) || DEFAULT_PROFILE_VERSION_ID;
19✔
527
    } else {
528
      logError(LOG_PRE_FIX + 'Config not found.');
×
529
      error = true;
×
530
    }
531

532
    if (!publisherId) {
19✔
533
      logError(LOG_PRE_FIX + 'Missing publisherId(Number).');
1✔
534
      error = true;
1✔
535
    }
536

537
    if (error) {
19✔
538
      logError(LOG_PRE_FIX + 'Not collecting data due to error(s).');
1✔
539
    } else {
540
      baseAdapter.enableAnalytics.call(this, conf);
18✔
541
    }
542
  },
543

544
  disableAnalytics() {
545
    publisherId = DEFAULT_PUBLISHER_ID;
18✔
546
    profileId = DEFAULT_PROFILE_ID;
18✔
547
    profileVersionId = DEFAULT_PROFILE_VERSION_ID;
18✔
548
    s2sBidders = [];
18✔
549
    baseAdapter.disableAnalytics.apply(this, arguments);
18✔
550
  },
551

552
  track({ eventType, args }) {
146✔
553
    const handler = eventHandlers[eventType];
146✔
554
    if (handler) {
146✔
555
      handler(args);
131✔
556
    }
557
  }
558
});
559

560
/// /////////// ADAPTER REGISTRATION //////////////
561

562
adapterManager.registerAnalyticsAdapter({
1✔
563
  adapter: pubmaticAdapter,
564
  code: ADAPTER_CODE
565
});
566

567
export default pubmaticAdapter;
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