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

prebid / Prebid.js / #306

01 Jul 2025 06:03PM UTC coverage: 90.335% (-0.07%) from 90.409%
#306

push

travis-ci

prebidjs-release
Prebid 9.53.1 release

43587 of 54762 branches covered (79.59%)

64461 of 71358 relevant lines covered (90.33%)

148.96 hits per line

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

95.45
/modules/rtdModule/index.js
1
/**
4✔
2
 * This module adds Real time data support to prebid.js
3
 * @module modules/realTimeData
4
 * @typedef {import('../../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig
5
 */
6

7
/**
8
 * @interface UserConsentData
9
 */
10
/**
11
 * @property
12
 * @summary gdpr consent
13
 * @name UserConsentData#gdpr
14
 * @type {Object}
15
 */
16
/**
17
 * @property
18
 * @summary usp consent
19
 * @name UserConsentData#usp
20
 * @type {Object}
21
 */
22
/**
23
 * @property
24
 * @summary coppa
25
 * @name UserConsentData#coppa
26
 * @type {boolean}
27
 */
28

29
/**
30
 * @interface RtdSubmodule
31
 */
32

33
/**
34
 * @function
35
 * @summary return real time data
36
 * @name RtdSubmodule#getTargetingData
37
 * @param {string[]} adUnitsCodes
38
 * @param {SubmoduleConfig} config
39
 * @param {UserConsentData} userConsent
40
 * @param {auction} auction
41
 */
42

43
/**
44
 * @function
45
 * @summary modify bid request data
46
 * @name RtdSubmodule#getBidRequestData
47
 * @param {Object} reqBidsConfigObj
48
 * @param {function} callback
49
 * @param {SubmoduleConfig} config
50
 * @param {UserConsentData} userConsent
51
 */
52

53
/**
54
 * @property
55
 * @summary used to link submodule with config
56
 * @name RtdSubmodule#name
57
 * @type {string}
58
 */
59

60
/**
61
 * @property
62
 * @summary used to link submodule with config
63
 * @name RtdSubmodule#config
64
 * @type {Object}
65
 */
66

67
/**
68
 * @function
69
 * @summary init sub module
70
 * @name RtdSubmodule#init
71
 * @param {SubmoduleConfig} config
72
 * @param {UserConsentData} user consent
73
 * @return {boolean} false to remove sub module
74
 */
75

76
/**
77
 * @function
78
 * @summary on auction init event
79
 * @name RtdSubmodule#onAuctionInitEvent
80
 * @param {Object} data
81
 * @param {SubmoduleConfig} config
82
 * @param {UserConsentData} userConsent
83
 */
84

85
/**
86
 * @function
87
 * @summary on auction end event
88
 * @name RtdSubmodule#onAuctionEndEvent
89
 * @param {Object} data
90
 * @param {SubmoduleConfig} config
91
 * @param {UserConsentData} userConsent
92
 */
93

94
/**
95
 * @function
96
 * @summary on bid response event
97
 * @name RtdSubmodule#onBidResponseEvent
98
 * @param {Object} data
99
 * @param {SubmoduleConfig} config
100
 * @param {UserConsentData} userConsent
101
 */
102

103
/**
104
 * @function
105
 * @summary on bid requested event
106
 * @name RtdSubmodule#onBidRequestEvent
107
 * @param {Object} data
108
 * @param {SubmoduleConfig} config
109
 * @param {UserConsentData} userConsent
110
 */
111

112
/**
113
 * @function
114
 * @summary on data deletion request
115
 * @name RtdSubmodule#onDataDeletionRequest
116
 * @param {SubmoduleConfig} config
117
 */
118

119
/**
120
 * @interface ModuleConfig
121
 */
122

123
/**
124
 * @property
125
 * @summary auction delay
126
 * @name ModuleConfig#auctionDelay
127
 * @type {number}
128
 */
129

130
/**
131
 * @property
132
 * @summary list of sub modules
133
 * @name ModuleConfig#dataProviders
134
 * @type {SubmoduleConfig[]}
135
 */
136

137
/**
138
 * @interface SubModuleConfig
139
 */
140

141
/**
142
 * @property
143
 * @summary params for provide (sub module)
144
 * @name SubModuleConfig#params
145
 * @type {Object}
146
 */
147

148
/**
149
 * @property
150
 * @summary name
151
 * @name ModuleConfig#name
152
 * @type {string}
153
 */
154

155
/**
156
 * @property
157
 * @summary delay auction for this sub module
158
 * @name ModuleConfig#waitForIt
159
 * @type {boolean}
160
 */
161

162
import {config} from '../../src/config.js';
163
import {getHook, module} from '../../src/hook.js';
164
import {logError, logInfo, logWarn, mergeDeep} from '../../src/utils.js';
165
import * as events from '../../src/events.js';
166
import { EVENTS, JSON_MAPPING } from '../../src/constants.js';
167
import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js';
168
import {timedAuctionHook} from '../../src/utils/perfMetrics.js';
169
import {GDPR_GVLIDS} from '../../src/consentHandler.js';
170
import {MODULE_TYPE_RTD} from '../../src/activities/modules.js';
171
import {guardOrtb2Fragments} from '../../libraries/objectGuard/ortbGuard.js';
172
import {activityParamsBuilder} from '../../src/activities/params.js';
173

174
const activityParams = activityParamsBuilder((al) => adapterManager.resolveAlias(al));
4✔
175

176
/** @type {string} */
177
const MODULE_NAME = 'realTimeData';
4✔
178
/** @type {RtdSubmodule[]} */
179
let registeredSubModules = [];
4✔
180
/** @type {RtdSubmodule[]} */
181
export let subModules = [];
4✔
182
/** @type {ModuleConfig} */
183
let _moduleConfig;
184
/** @type {SubmoduleConfig[]} */
185
let _dataProviders = [];
4✔
186
/** @type {UserConsentData} */
187
let _userConsent;
188

189
/**
190
 * Register a Real-Time Data (RTD) submodule.
191
 *
192
 * @param {Object} submodule The RTD submodule to register.
193
 * @param {string} submodule.name The name of the RTD submodule.
194
 * @param {number} [submodule.gvlid] The Global Vendor List ID (GVLID) of the RTD submodule.
195
 * @returns {function(): void} A de-registration function that will unregister the module when called.
196
 */
197
export function attachRealTimeDataProvider(submodule) {
198
  registeredSubModules.push(submodule);
116✔
199
  GDPR_GVLIDS.register(MODULE_TYPE_RTD, submodule.name, submodule.gvlid)
116✔
200
  return function detach() {
116✔
201
    const idx = registeredSubModules.indexOf(submodule)
61✔
202
    if (idx >= 0) {
61✔
203
      registeredSubModules.splice(idx, 1);
55✔
204
      initSubModules();
55✔
205
    }
206
  }
207
}
208

209
/**
210
 * call each sub module event function by config order
211
 */
212
const setEventsListeners = (function () {
4✔
213
  let registered = false;
4✔
214
  return function setEventsListeners() {
4✔
215
    if (!registered) {
19✔
216
      Object.entries({
1✔
217
        [EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'],
218
        [EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting],
219
        [EVENTS.BID_RESPONSE]: ['onBidResponseEvent'],
220
        [EVENTS.BID_REQUESTED]: ['onBidRequestEvent'],
221
        [EVENTS.BID_ACCEPTED]: ['onBidAcceptedEvent']
222
      }).forEach(([ev, [handler, preprocess]]) => {
223
        events.on(ev, (args) => {
5✔
224
          preprocess && preprocess(args);
9✔
225
          subModules.forEach(sm => {
9✔
226
            try {
18✔
227
              sm[handler] && sm[handler](args, sm.config, _userConsent)
18✔
228
            } catch (e) {
229
              logError(`RTD provider '${sm.name}': error in '${handler}':`, e);
4✔
230
            }
231
          });
232
        })
233
      });
234
      registered = true;
1✔
235
    }
236
  }
237
})();
238

239
export function init(config) {
240
  const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => {
23✔
241
    if (!realTimeData.dataProviders) {
19!
242
      logError('missing parameters for real time module');
×
243
      return;
×
244
    }
245
    confListener(); // unsubscribe config listener
19✔
246
    _moduleConfig = realTimeData;
19✔
247
    _dataProviders = realTimeData.dataProviders;
19✔
248
    setEventsListeners();
19✔
249
    getHook('startAuction').before(setBidRequestsData, 20); // RTD should run before FPD
19✔
250
    adapterManager.callDataDeletionRequest.before(onDataDeletionRequest);
19✔
251
    initSubModules();
19✔
252
  });
253
}
254

255
function getConsentData() {
256
  return {
77✔
257
    gdpr: gdprDataHandler.getConsentData(),
258
    usp: uspDataHandler.getConsentData(),
259
    gpp: gppDataHandler.getConsentData(),
260
    coppa: !!(config.getConfig('coppa'))
261
  }
262
}
263

264
/**
265
 * call each sub module init function by config order
266
 * if no init function / init return failure / module not configured - remove it from submodules list
267
 */
268
function initSubModules() {
269
  _userConsent = getConsentData();
74✔
270
  let subModulesByOrder = [];
74✔
271
  _dataProviders.forEach(provider => {
74✔
272
    const sm = ((registeredSubModules) || []).find(s => s.name === provider.name);
4,735!
273
    const initResponse = sm && sm.init && sm.init(provider, _userConsent);
220✔
274
    if (initResponse) {
220✔
275
      subModulesByOrder.push(Object.assign(sm, {config: provider}));
74✔
276
    }
277
  });
278
  subModules = subModulesByOrder;
74✔
279
  logInfo(`Real time data module enabled, using submodules: ${subModules.map((m) => m.name).join(', ')}`);
74✔
280
}
281

282
/**
283
 * loop through configured data providers If the data provider has registered getBidRequestData,
284
 * call it, providing reqBidsConfigObj, consent data and module params
285
 * this allows submodules to modify bidders
286
 * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids.
287
 * @param {function} fn required; The next function in the chain, used by hook.js
288
 */
289
export const setBidRequestsData = timedAuctionHook('rtd', function setBidRequestsData(fn, reqBidsConfigObj) {
4✔
290
  _userConsent = getConsentData();
3✔
291

292
  const relevantSubModules = [];
3✔
293
  const prioritySubModules = [];
3✔
294
  subModules.forEach(sm => {
3✔
295
    if (typeof sm.getBidRequestData !== 'function') {
6!
296
      return;
×
297
    }
298
    relevantSubModules.push(sm);
6✔
299
    const config = sm.config;
6✔
300
    if (config && config.waitForIt) {
6✔
301
      prioritySubModules.push(sm);
3✔
302
    }
303
  });
304

305
  const shouldDelayAuction = prioritySubModules.length && _moduleConfig?.auctionDelay > 0;
3✔
306
  let callbacksExpected = prioritySubModules.length;
3✔
307
  let isDone = false;
3✔
308
  let waitTimeout;
309
  const verifiers = [];
3✔
310

311
  if (!relevantSubModules.length) {
3!
312
    return exitHook();
×
313
  }
314

315
  const timeout = shouldDelayAuction ? _moduleConfig.auctionDelay : 0;
3!
316
  waitTimeout = setTimeout(exitHook, timeout);
3✔
317

318
  relevantSubModules.forEach(sm => {
3✔
319
    const fpdGuard = guardOrtb2Fragments(reqBidsConfigObj.ortb2Fragments || {}, activityParams(MODULE_TYPE_RTD, sm.name));
6✔
320
    verifiers.push(fpdGuard.verify);
6✔
321
    reqBidsConfigObj.ortb2Fragments = fpdGuard.obj;
6✔
322
    sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent, timeout);
6✔
323
  });
324

325
  function onGetBidRequestDataCallback() {
326
    if (isDone) {
4✔
327
      return;
1✔
328
    }
329
    if (this.config && this.config.waitForIt) {
3✔
330
      callbacksExpected--;
2✔
331
    }
332
    if (callbacksExpected === 0) {
3!
333
      setTimeout(exitHook, 0);
3✔
334
    }
335
  }
336

337
  function exitHook() {
338
    if (isDone) {
4✔
339
      return;
1✔
340
    }
341
    isDone = true;
3✔
342
    clearTimeout(waitTimeout);
3✔
343
    verifiers.forEach(fn => fn());
6✔
344
    fn.call(this, reqBidsConfigObj);
3✔
345
  }
346
});
347

348
/**
349
 * loop through configured data providers If the data provider has registered getTargetingData,
350
 * call it, providing ad unit codes, consent data and module params
351
 * the sub mlodle will return data to set on the ad unit
352
 * this function used to place key values on primary ad server per ad unit
353
 * @param {Object} auction object received on auction end event
354
 */
355
export function getAdUnitTargeting(auction) {
356
  const relevantSubModules = subModules.filter(sm => typeof sm.getTargetingData === 'function');
10✔
357
  if (!relevantSubModules.length) {
5✔
358
    return;
2✔
359
  }
360

361
  // get data
362
  const adUnitCodes = auction.adUnitCodes;
3✔
363
  if (!adUnitCodes) {
3!
364
    return;
×
365
  }
366
  let targeting = [];
3✔
367
  for (let i = relevantSubModules.length - 1; i >= 0; i--) {
3✔
368
    const smTargeting = relevantSubModules[i].getTargetingData(adUnitCodes, relevantSubModules[i].config, _userConsent, auction);
6✔
369
    if (smTargeting && typeof smTargeting === 'object') {
6✔
370
      targeting.push(smTargeting);
4✔
371
    } else {
372
      logWarn('invalid getTargetingData response for sub module', relevantSubModules[i].name);
2✔
373
    }
374
  }
375
  // place data on auction adUnits
376
  const mergedTargeting = mergeDeep({}, ...targeting);
3✔
377
  auction.adUnits.forEach(adUnit => {
3✔
378
    const kv = adUnit.code && mergedTargeting[adUnit.code];
5✔
379
    if (!kv) {
5✔
380
      return
1✔
381
    }
382
    logInfo('RTD set ad unit targeting of', kv, 'for', adUnit);
4✔
383
    adUnit[JSON_MAPPING.ADSERVER_TARGETING] = Object.assign(adUnit[JSON_MAPPING.ADSERVER_TARGETING] || {}, kv);
4✔
384
  });
385
  return auction.adUnits;
3✔
386
}
387

388
export function onDataDeletionRequest(next, ...args) {
389
  subModules.forEach((sm) => {
3✔
390
    if (typeof sm.onDataDeletionRequest === 'function') {
6✔
391
      try {
5✔
392
        sm.onDataDeletionRequest(sm.config);
5✔
393
      } catch (e) {
394
        logError(`Error executing ${sm.name}.onDataDeletionRequest`, e)
1✔
395
      }
396
    }
397
  });
398
  next.apply(this, args);
3✔
399
}
400

401
module('realTimeData', attachRealTimeDataProvider);
4✔
402
init(config);
4✔
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