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

prebid / Prebid.js / 19934292619

04 Dec 2025 03:28PM UTC coverage: 96.209%. Remained the same
19934292619

push

github

web-flow
fix tests path in CONTRIBUTING.md (#14235)

53792 of 65902 branches covered (81.62%)

205193 of 213279 relevant lines covered (96.21%)

71.92 hits per line

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

94.79
/modules/optableRtdProvider.js
1
import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
1✔
2
import {loadExternalScript} from '../src/adloader.js';
3
import {config} from '../src/config.js';
4
import {submodule} from '../src/hook.js';
5
import {deepAccess, mergeDeep, prefixLog} from '../src/utils.js';
6

7
const MODULE_NAME = 'optable';
1✔
8
export const LOG_PREFIX = `[${MODULE_NAME} RTD]:`;
1✔
9
const optableLog = prefixLog(LOG_PREFIX);
1✔
10
const {logMessage, logWarn, logError} = optableLog;
1✔
11

12
/**
13
 * Extracts the parameters for Optable RTD module from the config object passed at instantiation
14
 * @param {Object} moduleConfig Configuration object for the module
15
 */
16
export const parseConfig = (moduleConfig) => {
1✔
17
  let bundleUrl = deepAccess(moduleConfig, 'params.bundleUrl', null);
20✔
18
  const adserverTargeting = deepAccess(moduleConfig, 'params.adserverTargeting', true);
20✔
19
  const handleRtd = deepAccess(moduleConfig, 'params.handleRtd', null);
20✔
20
  const instance = deepAccess(moduleConfig, 'params.instance', null);
20✔
21

22
  // If present, trim the bundle URL
23
  if (typeof bundleUrl === 'string') {
20✔
24
    bundleUrl = bundleUrl.trim();
9✔
25
  }
26

27
  // Verify that bundleUrl is a valid URL: only secure (HTTPS) URLs are allowed
28
  if (typeof bundleUrl === 'string' && bundleUrl.length && !bundleUrl.startsWith('https://')) {
20✔
29
    logError('Invalid URL format for bundleUrl in moduleConfig. Only HTTPS URLs are allowed.');
5✔
30
    return {bundleUrl: null, adserverTargeting, handleRtd: null};
5✔
31
  }
32

33
  if (handleRtd && typeof handleRtd !== 'function') {
15✔
34
    logError('handleRtd must be a function');
2✔
35
    return {bundleUrl, adserverTargeting, handleRtd: null};
2✔
36
  }
37

38
  const result = {bundleUrl, adserverTargeting, handleRtd};
13✔
39
  if (instance !== null) {
13!
40
    result.instance = instance;
×
41
  }
42
  return result;
13✔
43
}
44

45
/**
46
 * Wait for Optable SDK event to fire with targeting data
47
 * @param {string} eventName Name of the event to listen for
48
 * @returns {Promise<Object|null>} Promise that resolves with targeting data or null
49
 */
50
const waitForOptableEvent = (eventName) => {
1✔
51
  return new Promise((resolve) => {
8✔
52
    const optableBundle = /** @type {Object} */ (window.optable);
8✔
53
    const cachedData = optableBundle?.instance?.targetingFromCache();
8✔
54

55
    if (cachedData && cachedData.ortb2) {
8✔
56
      logMessage('Optable SDK already has cached data');
2✔
57
      resolve(cachedData);
2✔
58
      return;
2✔
59
    }
60

61
    const eventListener = (event) => {
6✔
62
      logMessage(`Received ${eventName} event`);
6✔
63
      // Extract targeting data from event detail
64
      const targetingData = event.detail;
6✔
65
      window.removeEventListener(eventName, eventListener);
6✔
66
      resolve(targetingData);
6✔
67
    };
68

69
    window.addEventListener(eventName, eventListener);
6✔
70
    logMessage(`Waiting for ${eventName} event`);
6✔
71
  });
72
};
73

74
/**
75
 * Default function to handle/enrich RTD data
76
 * @param reqBidsConfigObj Bid request configuration object
77
 * @param optableExtraData Additional data to be used by the Optable SDK
78
 * @param mergeFn Function to merge data
79
 * @returns {Promise<void>}
80
 */
81
export const defaultHandleRtd = async (reqBidsConfigObj, optableExtraData, mergeFn) => {
1✔
82
  // Wait for the Optable SDK to dispatch targeting data via event
83
  let targetingData = await waitForOptableEvent('optable-targeting:change');
8✔
84

85
  if (!targetingData || !targetingData.ortb2) {
8✔
86
    logWarn('No targeting data found');
1✔
87
    return;
1✔
88
  }
89

90
  mergeFn(
7✔
91
    reqBidsConfigObj.ortb2Fragments.global,
92
    targetingData.ortb2,
93
  );
94
  logMessage('Prebid\'s global ORTB2 object after merge: ', reqBidsConfigObj.ortb2Fragments.global);
7✔
95
};
96

97
/**
98
 * Get data from Optable and merge it into the global ORTB2 object
99
 * @param {Function} handleRtdFn Function to handle RTD data
100
 * @param {Object} reqBidsConfigObj Bid request configuration object
101
 * @param {Object} optableExtraData Additional data to be used by the Optable SDK
102
 * @param {Function} mergeFn Function to merge data
103
 */
104
export const mergeOptableData = async (handleRtdFn, reqBidsConfigObj, optableExtraData, mergeFn) => {
1✔
105
  if (handleRtdFn.constructor.name === 'AsyncFunction') {
7✔
106
    await handleRtdFn(reqBidsConfigObj, optableExtraData, mergeFn);
4✔
107
  } else {
108
    handleRtdFn(reqBidsConfigObj, optableExtraData, mergeFn);
3✔
109
  }
110
};
111

112
/**
113
 * @param {Object} reqBidsConfigObj Bid request configuration object
114
 * @param {Function} callback Called on completion
115
 * @param {Object} moduleConfig Configuration for Optable RTD module
116
 * @param {Object} userConsent
117
 */
118
export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => {
1✔
119
  try {
6✔
120
    // Extract the bundle URL from the module configuration
121
    const {bundleUrl, handleRtd} = parseConfig(moduleConfig);
6✔
122
    const handleRtdFn = handleRtd || defaultHandleRtd;
6✔
123
    const optableExtraData = config.getConfig('optableRtdConfig') || {};
6✔
124

125
    if (bundleUrl) {
6✔
126
      // If bundleUrl is present, load the Optable JS bundle
127
      // by using the loadExternalScript function
128
      logMessage('Custom bundle URL found in config: ', bundleUrl);
1✔
129

130
      // Load Optable JS bundle and merge the data
131
      loadExternalScript(bundleUrl, MODULE_TYPE_RTD, MODULE_NAME, () => {
1✔
132
        logMessage('Successfully loaded Optable JS bundle');
1✔
133
        mergeOptableData(handleRtdFn, reqBidsConfigObj, optableExtraData, mergeDeep).then(callback, callback);
1✔
134
      }, document);
135
    } else {
136
      // At this point, we assume that the Optable JS bundle is already
137
      // present on the page. If it is, we can directly merge the data
138
      // by passing the callback to the optable.cmd.push function.
139
      logMessage('Custom bundle URL not found in config. ' +
5✔
140
        'Assuming Optable JS bundle is already present on the page');
141
      window.optable = window.optable || { cmd: [] };
5✔
142
      window.optable.cmd.push(() => {
5✔
143
        logMessage('Optable JS bundle found on the page');
4✔
144
        mergeOptableData(handleRtdFn, reqBidsConfigObj, optableExtraData, mergeDeep).then(callback, callback);
4✔
145
      });
146
    }
147
  } catch (error) {
148
    // If an error occurs, log it and call the callback
149
    // to continue with the auction
150
    logError(error);
×
151
    callback();
×
152
  }
153
}
154

155
/**
156
 * Get Optable targeting data and merge it into the ad units
157
 * @param adUnits Array of ad units
158
 * @param moduleConfig Module configuration
159
 * @param userConsent User consent
160
 * @param auction Auction object
161
 * @returns {Object} Targeting data
162
 */
163
export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) => {
1✔
164
  // Extract `adserverTargeting` and `instance` from the module configuration
165
  const {adserverTargeting, instance} = parseConfig(moduleConfig);
5✔
166
  logMessage('Ad Server targeting: ', adserverTargeting);
5✔
167

168
  if (!adserverTargeting) {
5✔
169
    logMessage('Ad server targeting is disabled');
1✔
170
    return {};
1✔
171
  }
172

173
  const targetingData = {};
4✔
174
  // Resolve the SDK instance object based on the instance string
175
  // Default to 'instance' if not provided
176
  const instanceKey = instance || 'instance';
4✔
177
  const sdkInstance = window?.optable?.[instanceKey];
4!
178
  if (!sdkInstance) {
4!
179
    logWarn(`No Optable SDK instance found for: ${instanceKey}`);
×
180
    return targetingData;
×
181
  }
182

183
  // Get the Optable targeting data from the cache
184
  const optableTargetingData = sdkInstance?.targetingKeyValuesFromCache?.() || targetingData;
4!
185

186
  // If no Optable targeting data is found, return an empty object
187
  if (!Object.keys(optableTargetingData).length) {
4✔
188
    logWarn('No Optable targeting data found');
1✔
189
    return targetingData;
1✔
190
  }
191

192
  // Merge the Optable targeting data into the ad units
193
  adUnits.forEach(adUnit => {
3✔
194
    targetingData[adUnit] = targetingData[adUnit] || {};
3✔
195
    mergeDeep(targetingData[adUnit], optableTargetingData);
3✔
196
  });
197

198
  // If the key contains no data, remove it
199
  Object.keys(targetingData).forEach((adUnit) => {
3✔
200
    Object.keys(targetingData[adUnit]).forEach((key) => {
3✔
201
      if (!targetingData[adUnit][key] || !targetingData[adUnit][key].length) {
5✔
202
        delete targetingData[adUnit][key];
4✔
203
      }
204
    });
205

206
    // If the ad unit contains no data, remove it
207
    if (!Object.keys(targetingData[adUnit]).length) {
3✔
208
      delete targetingData[adUnit];
2✔
209
    }
210
  });
211

212
  logMessage('Optable targeting data: ', targetingData);
3✔
213
  return targetingData;
3✔
214
};
215

216
/**
217
 * Dummy init function
218
 * @param {Object} config Module configuration
219
 * @param {boolean} userConsent User consent
220
 * @returns true
221
 */
222
const init = (config, userConsent) => {
1✔
223
  return true;
1✔
224
}
225

226
// Optable RTD submodule
227
export const optableSubmodule = {
1✔
228
  name: MODULE_NAME,
229
  init,
230
  getBidRequestData,
231
  getTargetingData,
232
}
233

234
// Register the Optable RTD submodule
235
submodule('realTimeData', optableSubmodule);
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