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

prebid / Prebid.js / #305

27 Jun 2025 12:10PM UTC coverage: 90.409% (-0.01%) from 90.422%
#305

push

travis-ci

prebidjs-release
Prebid 9.52.0 release

43449 of 54513 branches covered (79.7%)

64185 of 70994 relevant lines covered (90.41%)

174.59 hits per line

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

98.32
/modules/sharethroughBidAdapter.js
1
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
1✔
2
import { config } from '../src/config.js';
3
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
4
import { prepareSplitImps } from '../libraries/equativUtils/equativUtils.js';
5
import { registerBidder } from '../src/adapters/bidderFactory.js';
6
import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js';
7

8
const VERSION = '4.3.0';
1✔
9
const BIDDER_CODE = 'sharethrough';
1✔
10
const SUPPLY_ID = 'WYu2BXv1';
1✔
11

12
const EQT_ENDPOINT = 'https://ssb.smartadserver.com/api/bid?callerId=233';
1✔
13
const STR_ENDPOINT = `https://btlr.sharethrough.com/universal/v1?supply_id=${SUPPLY_ID}`;
1✔
14
const IDENTIFIER_PREFIX = 'Sharethrough:';
1✔
15

16
const impIdMap = {};
1✔
17
let isEqtvTest = null;
1✔
18

19
/**
20
 * Gets value of the local variable impIdMap
21
 * @returns {*} Value of impIdMap
22
 */
23
export function getImpIdMap() {
24
  return impIdMap;
1✔
25
}
26

27
/**
28
 * Sets value of the local variable isEqtvTest
29
 * @param {*} value
30
 */
31
export function setIsEqtvTest(value) {
32
  isEqtvTest = value;
55✔
33
}
34

35
// this allows stubbing of utility function that is used internally by the sharethrough adapter
36
export const sharethroughInternal = {
1✔
37
  getProtocol,
38
};
39

40
export const converter = ortbConverter({
1✔
41
  context: {
42
    netRevenue: true,
43
    ttl: 360
44
  }
45
});
46

47
export const sharethroughAdapterSpec = {
1✔
48
  code: BIDDER_CODE,
49
  supportedMediaTypes: [VIDEO, BANNER, NATIVE],
50
  gvlid: 80,
51
  isBidRequestValid: (bid) => !!bid.params.pkey,
2✔
52

53
  buildRequests: (bidRequests, bidderRequest) => {
54
    const timeout = bidderRequest.timeout;
53✔
55
    const firstPartyData = bidderRequest.ortb2 || {};
53✔
56

57
    const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0;
53✔
58
    const secure = nonHttp || sharethroughInternal.getProtocol().indexOf('https') > -1;
53✔
59

60
    const req = {
53✔
61
      id: generateUUID(),
62
      at: 1,
63
      cur: ['USD'],
64
      tmax: timeout,
65
      site: {
66
        domain: deepAccess(bidderRequest, 'refererInfo.domain', window.location.hostname),
67
        page: deepAccess(bidderRequest, 'refererInfo.page', window.location.href),
68
        ref: deepAccess(bidderRequest, 'refererInfo.ref'),
69
        ...firstPartyData.site,
70
      },
71
      device: {
72
        ua: navigator.userAgent,
73
        language: navigator.language,
74
        js: 1,
75
        dnt: navigator.doNotTrack === '1' ? 1 : 0,
53!
76
        h: window.screen.height,
77
        w: window.screen.width,
78
        ext: {},
79
      },
80
      regs: {
81
        coppa: config.getConfig('coppa') === true ? 1 : 0,
53✔
82
        ext: {},
83
      },
84
      source: {
85
        tid: bidderRequest.ortb2?.source?.tid,
86
        ext: {
87
          version: '$prebid.version$',
88
          str: VERSION,
89
          schain: bidRequests[0].schain,
90
        },
91
      },
92
      bcat: deepAccess(bidderRequest.ortb2, 'bcat') || bidRequests[0].params.bcat || [],
111✔
93
      badv: deepAccess(bidderRequest.ortb2, 'badv') || bidRequests[0].params.badv || [],
111✔
94
      test: 0,
95
    };
96

97
    if (bidRequests[0].params.equativNetworkId) {
53✔
98
      isEqtvTest = true;
8✔
99
      req.site.publisher = {
8✔
100
        id: bidRequests[0].params.equativNetworkId,
101
        ...req.site.publisher
102
      };
103
    }
104

105
    if (bidderRequest.ortb2?.device?.ext?.cdep) {
53✔
106
      req.device.ext['cdep'] = bidderRequest.ortb2.device.ext.cdep;
1✔
107
    }
108

109
    // if present, merge device object from ortb2 into `req.device`
110
    if (bidderRequest?.ortb2?.device) {
53✔
111
      mergeDeep(req.device, bidderRequest.ortb2.device);
3✔
112
    }
113

114
    req.user = nullish(firstPartyData.user, {});
53✔
115
    if (!req.user.ext) req.user.ext = {};
53✔
116
    req.user.ext.eids = bidRequests[0].userIdAsEids || [];
53✔
117

118
    if (bidderRequest.gdprConsent) {
53✔
119
      const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true;
2✔
120
      req.regs.ext.gdpr = gdprApplies ? 1 : 0;
2✔
121
      if (gdprApplies) {
2✔
122
        req.user.ext.consent = bidderRequest.gdprConsent.consentString;
1✔
123
      }
124
    }
125

126
    if (bidderRequest.uspConsent) {
53✔
127
      req.regs.ext.us_privacy = bidderRequest.uspConsent;
1✔
128
      req.regs.us_privacy = bidderRequest.uspConsent;
1✔
129
    }
130

131
    if (bidderRequest?.gppConsent?.gppString) {
53✔
132
      req.regs.gpp = bidderRequest.gppConsent.gppString;
1✔
133
      req.regs.gpp_sid = bidderRequest.gppConsent.applicableSections;
1✔
134
    } else if (bidderRequest?.ortb2?.regs?.gpp) {
52✔
135
      req.regs.ext.gpp = bidderRequest.ortb2.regs.gpp;
4✔
136
      req.regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid;
4✔
137
    }
138

139
    if (bidderRequest?.ortb2?.regs?.ext?.dsa) {
53✔
140
      req.regs.ext.dsa = bidderRequest.ortb2.regs.ext.dsa;
1✔
141
    }
142

143
    const imps = bidRequests
53✔
144
      .map((bidReq) => {
145
        const impression = { ext: {} };
96✔
146

147
        // mergeDeep(impression, bidReq.ortb2Imp); // leaving this out for now as we may want to leave stuff out on purpose
148
        const tid = deepAccess(bidReq, 'ortb2Imp.ext.tid');
96✔
149
        if (tid) impression.ext.tid = tid;
96✔
150
        const gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid') || deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot');
96✔
151
        if (gpid) impression.ext.gpid = gpid;
96✔
152

153
        const nativeRequest = deepAccess(bidReq, 'mediaTypes.native');
96✔
154
        const videoRequest = deepAccess(bidReq, 'mediaTypes.video');
96✔
155

156
        if (bidderRequest.paapi?.enabled && bidReq.mediaTypes.banner) {
96✔
157
          mergeDeep(impression, { ext: { ae: 1 } }); // ae = auction environment; if this is 1, ad server knows we have a fledge auction
1✔
158
        }
159

160
        if (videoRequest) {
96✔
161
          // default playerSize, only change this if we know width and height are properly defined in the request
162
          let [w, h] = [640, 360];
45✔
163
          if (
45✔
164
            videoRequest.playerSize &&
177✔
165
            videoRequest.playerSize[0] &&
166
            videoRequest.playerSize[0][0] &&
167
            videoRequest.playerSize[0][1]
168
          ) {
169
            [w, h] = videoRequest.playerSize[0];
44✔
170
          }
171

172
          /**
173
           * Applies a specified property to an impression object if it is present in the video request
174
           * @param {string} prop A property to apply to the impression object
175
           * @param {object} vidReq A video request object from which to extract the property
176
           * @param {object} imp A video impression object to which to apply the property
177
           */
178
          const applyVideoProperty = (prop, vidReq, imp) => {
45✔
179
            const propIsTypeArray = ['api', 'battr', 'mimes', 'playbackmethod', 'protocols'].includes(prop);
764✔
180
            if (propIsTypeArray) {
764✔
181
              const notAssignable = (!Array.isArray(vidReq[prop]) || vidReq[prop].length === 0) && vidReq[prop];
225✔
182
              if (notAssignable) {
225✔
183
                logWarn(`${IDENTIFIER_PREFIX} Invalid video request property: "${prop}" must be an array with at least 1 entry.  Value supplied: "${vidReq[prop]}".  This will not be added to the bid request.`);
3✔
184
                return;
3✔
185
              }
186
            }
187
            if (vidReq[prop]) {
761✔
188
              imp.video[prop] = vidReq[prop];
736✔
189
            }
190
          };
191

192
          impression.video = {
45✔
193
            pos: nullish(videoRequest.pos, 0),
194
            topframe: inIframe() ? 0 : 1,
45!
195
            w,
196
            h,
197
          };
198

199
          const propertiesToConsider = [
45✔
200
            'api', 'battr', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay'
201
          ];
202

203
          if (!isEqtvTest) {
45✔
204
            propertiesToConsider.push('companionad');
44✔
205
          }
206

207
          propertiesToConsider.forEach(propertyToConsider => {
45✔
208
            applyVideoProperty(propertyToConsider, videoRequest, impression);
764✔
209
          });
210
        } else if (isEqtvTest && nativeRequest) {
51✔
211
          const nativeImp = converter.toORTB({
2✔
212
            bidRequests: [bidReq],
213
            bidderRequest
214
          });
215

216
          impression.native = {
2✔
217
            ...nativeImp.imp[0].native
218
          };
219
        } else {
220
          impression.banner = {
49✔
221
            pos: deepAccess(bidReq, 'mediaTypes.banner.pos', 0),
222
            topframe: inIframe() ? 0 : 1,
49!
223
            format: bidReq.sizes.map((size) => ({ w: +size[0], h: +size[1] })),
98✔
224
          };
225
          const battr = deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr');
49✔
226
          if (battr) impression.banner.battr = battr;
49✔
227
        }
228

229
        const tagid = isEqtvTest ? bidReq.adUnitCode : String(bidReq.params.pkey);
96✔
230

231
        return {
96✔
232
          id: bidReq.bidId,
233
          tagid,
234
          secure: secure ? 1 : 0,
96✔
235
          bidfloor: getBidRequestFloor(bidReq),
236
          ...impression,
237
        };
238
      })
239
      .filter((imp) => !!imp);
96✔
240

241
    let splitImps = []
53✔
242
    if (isEqtvTest) {
53✔
243
      const bid = bidRequests[0];
8✔
244
      const currency = config.getConfig('currency.adServerCurrency') || 'USD';
8✔
245
      splitImps = prepareSplitImps(imps, bid, currency, impIdMap, 'stx');
8✔
246
    }
247

248
    return imps.map((impression) => {
53✔
249
      return {
96✔
250
        method: 'POST',
251
        url: isEqtvTest ? EQT_ENDPOINT : STR_ENDPOINT,
96✔
252
        data: {
253
          ...req,
254
          imp: isEqtvTest ? splitImps : [impression],
96✔
255
        },
256
      };
257
    });
258
  },
259

260
  interpretResponse: ({ body }, req) => {
261
    if (
9!
262
      !body ||
45✔
263
      !body.seatbid ||
264
      body.seatbid.length === 0 ||
265
      !body.seatbid[0].bid ||
266
      body.seatbid[0].bid.length === 0
267
    ) {
268
      return [];
×
269
    }
270

271
    const fledgeAuctionEnabled = body.ext?.auctionConfigs;
9✔
272

273
    const imp = req.data.imp[0];
9✔
274

275
    const bidsFromExchange = body.seatbid[0].bid.map((bid) => {
9✔
276
      // Spec: https://docs.prebid.org/dev-docs/bidder-adaptor.html#interpreting-the-response
277
      const response = {
11✔
278
        requestId: isEqtvTest ? impIdMap[bid.impid] : bid.impid,
11✔
279
        width: +bid.w,
280
        height: +bid.h,
281
        cpm: +bid.price,
282
        creativeId: bid.crid,
283
        dealId: bid.dealid || null,
14✔
284
        mediaType: imp.video ? VIDEO : imp.native ? NATIVE : BANNER,
21✔
285
        currency: body.cur || 'USD',
22✔
286
        netRevenue: true,
287
        ttl: typeof bid.exp === 'number' && bid.exp > 0 ? bid.exp : 360,
26✔
288
        ad: bid.adm,
289
        nurl: bid.nurl,
290
        meta: {
291
          advertiserDomains: bid.adomain || [],
14✔
292
          networkId: bid.ext?.networkId || null,
21✔
293
          networkName: bid.ext?.networkName || null,
21✔
294
          agencyId: bid.ext?.agencyId || null,
21✔
295
          agencyName: bid.ext?.agencyName || null,
21✔
296
          advertiserId: bid.ext?.advertiserId || null,
21✔
297
          advertiserName: bid.ext?.advertiserName || null,
21✔
298
          brandId: bid.ext?.brandId || null,
21✔
299
          brandName: bid.ext?.brandName || null,
21✔
300
          demandSource: bid.ext?.demandSource || null,
21✔
301
          dchain: bid.ext?.dchain || null,
21✔
302
          primaryCatId: bid.ext?.primaryCatId || null,
21✔
303
          secondaryCatIds: bid.ext?.secondaryCatIds || null,
21✔
304
          mediaType: bid.ext?.mediaType || null,
21✔
305
        },
306
      };
307

308
      if (response.mediaType === VIDEO) {
11✔
309
        response.ttl = 3600;
1✔
310
        response.vastXml = bid.adm;
1✔
311
      } else if (response.mediaType === NATIVE) {
10✔
312
        response.native = {
1✔
313
          ortb: JSON.parse(bid.adm)
314
        };
315
      }
316

317
      return response;
11✔
318
    });
319

320
    if (fledgeAuctionEnabled && !isEqtvTest) {
9✔
321
      return {
1✔
322
        bids: bidsFromExchange,
323
        paapi: body.ext?.auctionConfigs || {},
1!
324
      };
325
    } else {
326
      return bidsFromExchange;
8✔
327
    }
328
  },
329

330
  getUserSyncs: (syncOptions, serverResponses) => {
331
    const shouldCookieSync =
332
      syncOptions.pixelEnabled && deepAccess(serverResponses, '0.body.cookieSyncUrls') !== undefined;
5✔
333

334
    return shouldCookieSync ? serverResponses[0].body.cookieSyncUrls.map((url) => ({ type: 'image', url: url })) : [];
5✔
335
  },
336

337
  // Empty implementation for prebid core to be able to find it
338
  onTimeout: (data) => { },
339

340
  // Empty implementation for prebid core to be able to find it
341
  onBidWon: (bid) => { },
342

343
  // Empty implementation for prebid core to be able to find it
344
  onSetTargeting: (bid) => { },
345
};
346

347
function getBidRequestFloor(bid) {
348
  let floor = null;
96✔
349
  if (typeof bid.getFloor === 'function') {
96✔
350
    const floorInfo = bid.getFloor({
90✔
351
      currency: 'USD',
352
      mediaType: bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner',
270✔
353
      size: bid.sizes.map((size) => ({ w: size[0], h: size[1] })),
134✔
354
    });
355
    if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
90✔
356
      floor = parseFloat(floorInfo.floor);
88✔
357
    }
358
  }
359
  return floor !== null ? floor : 0;
96✔
360
}
361

362
function getProtocol() {
363
  return window.location.protocol;
×
364
}
365

366
// stub for ?? operator
367
function nullish(input, def) {
368
  return input === null || input === undefined ? def : input;
98✔
369
}
370

371
registerBidder(sharethroughAdapterSpec);
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