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

prebid / Prebid.js / 16976294738

14 Aug 2025 08:45PM UTC coverage: 96.25% (-0.001%) from 96.251%
16976294738

push

github

web-flow
fluct Bid Adapter : add instl support (#13439)

* add instl support

* falseの場合も明示的に送信するようにした

39472 of 48509 branches covered (81.37%)

11 of 11 new or added lines in 2 files covered. (100.0%)

23 existing lines in 11 files now uncovered.

195599 of 203219 relevant lines covered (96.25%)

124.45 hits per line

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

93.22
/modules/topicsFpdModule.js
1
import {isEmpty, logError, logWarn, mergeDeep, safeJSONParse} from '../src/utils.js';
1✔
2
import {getRefererInfo} from '../src/refererDetection.js';
3
import {submodule} from '../src/hook.js';
4
import {PbPromise} from '../src/utils/promise.js';
5
import {config} from '../src/config.js';
6
import {getCoreStorageManager} from '../src/storageManager.js';
7

8
import {isActivityAllowed} from '../src/activities/rules.js';
9
import {ACTIVITY_ENRICH_UFPD} from '../src/activities/activities.js';
10
import {activityParams} from '../src/activities/activityParams.js';
11
import {MODULE_TYPE_BIDDER} from '../src/activities/modules.js';
12

13
const MODULE_NAME = 'topicsFpd';
1✔
14
const DEFAULT_EXPIRATION_DAYS = 21;
1✔
15
const DEFAULT_FETCH_RATE_IN_DAYS = 1;
1✔
16
let LOAD_TOPICS_INITIALISE = false;
1✔
17
let iframeLoadedURL = [];
1✔
18

19
export function reset() {
20
  LOAD_TOPICS_INITIALISE = false;
35✔
21
  iframeLoadedURL = [];
35✔
22
}
23

24
export const coreStorage = getCoreStorageManager(MODULE_NAME);
1✔
25
export const topicStorageName = 'prebid:topics';
1✔
26
export const lastUpdated = 'lastUpdated';
1✔
27

28
const TAXONOMIES = {
1✔
29
  // map from topic taxonomyVersion to IAB segment taxonomy
30
  '1': 600,
31
  '2': 601,
32
  '3': 602,
33
  '4': 603
34
}
35

36
function partitionBy(field, items) {
37
  return items.reduce((partitions, item) => {
29✔
38
    const key = item[field];
46✔
39
    if (!partitions.hasOwnProperty(key)) partitions[key] = [];
46✔
40
    partitions[key].push(item);
46✔
41
    return partitions;
46✔
42
  }, {});
43
}
44

45
/**
46
 * function to get list of loaded Iframes calling Topics API
47
 */
48
function getLoadedIframeURL() {
49
  return iframeLoadedURL;
8✔
50
}
51

52
/**
53
 * function to set/push iframe in the list which is loaded to called topics API.
54
 */
55
function setLoadedIframeURL(url) {
56
  return iframeLoadedURL.push(url);
2✔
57
}
58

59
export function getTopicsData(name, topics, taxonomies = TAXONOMIES) {
15✔
60
  return Object.entries(partitionBy('taxonomyVersion', topics))
15✔
61
    .filter(([taxonomyVersion]) => {
16✔
62
      if (!taxonomies.hasOwnProperty(taxonomyVersion)) {
16✔
63
        logWarn(`Unrecognized taxonomyVersion from Topics API: "${taxonomyVersion}"; topic will be ignored`);
2✔
64
        return false;
2✔
65
      }
66
      return true;
14✔
67
    }).flatMap(([taxonomyVersion, topics]) =>
14✔
68
      Object.entries(partitionBy('modelVersion', topics))
69
        .map(([modelVersion, topics]) => {
18✔
70
          const datum = {
18✔
71
            ext: {
72
              segtax: taxonomies[taxonomyVersion],
73
              segclass: modelVersion
74
            },
75
            segment: topics.map((topic) => ({id: topic.topic.toString()}))
22✔
76
          };
77
          if (name != null) {
18✔
78
            datum.name = name;
10✔
79
          }
80

81
          return datum;
18✔
82
        })
83
    );
84
}
85

86
function isTopicsSupported(doc = document) {
18!
87
  return 'browsingTopics' in doc && doc.featurePolicy.allowsFeature('browsing-topics')
18✔
88
}
89

90
export function getTopics(doc = document) {
6✔
91
  let topics = null;
6✔
92

93
  try {
6✔
94
    if (isTopicsSupported(doc)) {
6✔
95
      topics = PbPromise.resolve(doc.browsingTopics());
2✔
96
    }
97
  } catch (e) {
98
    logError('Could not call topics API', e);
2✔
99
  }
100
  if (topics == null) {
6✔
101
    topics = PbPromise.resolve([]);
5✔
102
  }
103

104
  return topics;
6✔
105
}
106

107
const topicsData = getTopics().then((topics) => getTopicsData(getRefererInfo().domain, topics));
1✔
108

109
export function processFpd(config, {global}, {data = topicsData} = {}) {
80✔
110
  if (!LOAD_TOPICS_INITIALISE) {
80✔
111
    loadTopicsForBidders();
4✔
112
    LOAD_TOPICS_INITIALISE = true;
4✔
113
  }
114
  return data.then((data) => {
80✔
115
    data = [].concat(data, getCachedTopics()); // Add cached data in FPD data.
80✔
116
    if (data.length) {
80✔
117
      mergeDeep(global, {
2✔
118
        user: {
119
          data
120
        }
121
      });
122
    }
123
    return {global};
80✔
124
  });
125
}
126

127
/**
128
 * function to fetch the cached topic data from storage for bidders and return it
129
 */
130
export function getCachedTopics() {
131
  const cachedTopicData = [];
84✔
132
  const topics = config.getConfig('userSync.topics');
84✔
133
  const bidderList = topics?.bidders || [];
84✔
134
  const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName)));
84✔
135
  storedSegments && storedSegments.forEach((value, cachedBidder) => {
84✔
136
    // Check bidder exist in config for cached bidder data and then only retrieve the cached data
137
    const bidderConfigObj = bidderList.find(({bidder}) => cachedBidder === bidder)
3✔
138
    if (bidderConfigObj && isActivityAllowed(ACTIVITY_ENRICH_UFPD, activityParams(MODULE_TYPE_BIDDER, cachedBidder))) {
3✔
139
      if (!isCachedDataExpired(value[lastUpdated], bidderConfigObj?.expiry || DEFAULT_EXPIRATION_DAYS)) {
1!
140
        Object.keys(value).forEach((segData) => {
1✔
141
          segData !== lastUpdated && cachedTopicData.push(value[segData]);
2✔
142
        })
143
      } else {
144
        // delete the specific bidder map from the store and store the updated maps
145
        storedSegments.delete(cachedBidder);
×
146
        coreStorage.setDataInLocalStorage(topicStorageName, JSON.stringify([...storedSegments]));
×
147
      }
148
    }
149
  });
150
  return cachedTopicData;
84✔
151
}
152

153
/**
154
 * Receive messages from iframe loaded for bidders to fetch topic
155
 * @param {MessageEvent} evt
156
 */
157
export function receiveMessage(evt) {
158
  if (evt && evt.data) {
8✔
159
    try {
8✔
160
      const data = safeJSONParse(evt.data);
8✔
161
      if (getLoadedIframeURL().includes(evt.origin) && data && data.segment && !isEmpty(data.segment.topics)) {
8✔
162
        const {domain, topics, bidder} = data.segment;
2✔
163
        const iframeTopicsData = getTopicsData(domain, topics);
2✔
164
        iframeTopicsData && storeInLocalStorage(bidder, iframeTopicsData);
2✔
165
      }
166
    } catch (err) { }
167
  }
168
}
169

170
/**
171
Function to store Topics data received from iframe in storage(name: "prebid:topics")
172
 * @param {string} bidder
173
 * @param {object} topics
174
 */
175
export function storeInLocalStorage(bidder, topics) {
176
  const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName)));
2✔
177
  const topicsObj = {
2✔
178
    [lastUpdated]: new Date().getTime()
179
  };
180

181
  topics.forEach((topic) => {
2✔
182
    topicsObj[topic.ext.segclass] = topic;
2✔
183
  });
184

185
  storedSegments.set(bidder, topicsObj);
2✔
186
  coreStorage.setDataInLocalStorage(topicStorageName, JSON.stringify([...storedSegments]));
2✔
187
}
188

189
function isCachedDataExpired(storedTime, cacheTime) {
190
  const _MS_PER_DAY = 1000 * 60 * 60 * 24;
2✔
191
  const currentTime = new Date().getTime();
2✔
192
  const daysDifference = Math.ceil((currentTime - storedTime) / _MS_PER_DAY);
2✔
193
  return daysDifference > cacheTime;
2✔
194
}
195

196
/**
197
 * Function to get random bidders based on count passed with array of bidders
198
 */
199
function getRandomAllowedConfigs(arr, count) {
200
  const configs = [];
6✔
201
  for (const config of [...arr].sort(() => 0.5 - Math.random())) {
6✔
202
    if (config.bidder && isActivityAllowed(ACTIVITY_ENRICH_UFPD, activityParams(MODULE_TYPE_BIDDER, config.bidder))) {
6✔
203
      configs.push(config);
5✔
204
    }
205
    if (configs.length >= count) {
6!
UNCOV
206
      break;
×
207
    }
208
  }
209
  return configs;
6✔
210
}
211

212
/**
213
 * function to add listener for message receiving from IFRAME
214
 */
215
function listenMessagesFromTopicIframe() {
216
  window.addEventListener('message', receiveMessage, false);
6✔
217
}
218

219
/**
220
 * function to load the iframes of the bidder to load the topics data
221
 */
222
export function loadTopicsForBidders(doc = document) {
12✔
223
  if (!isTopicsSupported(doc)) return;
12✔
224
  const topics = config.getConfig('userSync.topics');
6✔
225

226
  if (topics) {
6!
227
    listenMessagesFromTopicIframe();
6✔
228
    const randomBidders = getRandomAllowedConfigs(topics.bidders || [], topics.maxTopicCaller || 1)
6!
229
    randomBidders && randomBidders.forEach(({ bidder, iframeURL, fetchUrl, fetchRate }) => {
6✔
230
      if (bidder && iframeURL) {
5✔
231
        const ifrm = doc.createElement('iframe');
2✔
232
        ifrm.name = 'ifrm_'.concat(bidder);
2✔
233
        ifrm.src = ''.concat(iframeURL, '?bidder=').concat(bidder);
2✔
234
        ifrm.style.display = 'none';
2✔
235
        setLoadedIframeURL(new URL(iframeURL).origin);
2✔
236
        iframeURL && doc.documentElement.appendChild(ifrm);
2✔
237
      }
238

239
      if (bidder && fetchUrl) {
5✔
240
        const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName)));
2✔
241
        const bidderLsEntry = storedSegments.get(bidder);
2✔
242

243
        if (!bidderLsEntry || (bidderLsEntry && isCachedDataExpired(bidderLsEntry[lastUpdated], fetchRate || DEFAULT_FETCH_RATE_IN_DAYS))) {
2!
244
          window.fetch(`${fetchUrl}?bidder=${bidder}`, {browsingTopics: true})
1✔
245
            .then(response => {
246
              return response.json();
1✔
247
            })
248
            .then(data => {
UNCOV
249
              if (data && data.segment && !isEmpty(data.segment.topics)) {
×
250
                const {domain, topics, bidder} = data.segment;
×
UNCOV
251
                const fetchTopicsData = getTopicsData(domain, topics);
×
UNCOV
252
                fetchTopicsData && storeInLocalStorage(bidder, fetchTopicsData);
×
253
              }
254
            });
255
        }
256
      }
257
    })
258
  } else {
UNCOV
259
    logWarn(`Topics config not defined under userSync Object`);
×
260
  }
261
}
262

263
submodule('firstPartyData', {
1✔
264
  name: 'topics',
265
  queue: 1,
266
  processFpd
267
});
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