Skip to content

Commit 79e6dbf

Browse files
authored
sevio Bid Adapter : add extra parameters required by the BE (prebid#13904)
* Add extra parameters required by the BE * Correct prod endpoint * Removed properties that were flagged as fingerprinting * Added tests for the additions made and also some extra safety checks in the adapter * Removed networkBandwidth and networkQuality as they are flagged as fingerprinting * Placed getDomComplexity under the fpdUtils/pageInfo.js * Use the getPageDescription and getPagetitle from the fpdUtils * Remove the tests for the params that are not sent anymore
1 parent 38cff0d commit 79e6dbf

3 files changed

Lines changed: 209 additions & 3 deletions

File tree

libraries/fpdUtils/pageInfo.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,12 @@ export function getReferrer(bidRequest = {}, bidderRequest = {}) {
6969
}
7070
return pageUrl;
7171
}
72+
73+
/**
74+
* get the document complexity
75+
* @param document
76+
* @returns {*|number}
77+
*/
78+
export function getDomComplexity(document) {
79+
return document?.querySelectorAll('*')?.length ?? -1;
80+
}

modules/sevioBidAdapter.js

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { detectWalletsPresence} from "../libraries/cryptoUtils/wallets.js";
33
import { registerBidder } from "../src/adapters/bidderFactory.js";
44
import { BANNER, NATIVE } from "../src/mediaTypes.js";
55
import { config } from "../src/config.js";
6+
import {getDomComplexity, getPageDescription, getPageTitle} from "../libraries/fpdUtils/pageInfo.js";
67
import * as converter from '../libraries/ortbConverter/converter.js';
8+
9+
const PREBID_VERSION = '$prebid.version$';
10+
const ADAPTER_VERSION = '1.0';
711
const ORTB = converter.ortbConverter({
812
context: { ttl: 300 }
913
});
@@ -17,6 +21,10 @@ const detectAdType = (bid) =>
1721
["native", "banner"].find((t) => bid.mediaTypes?.[t]) || "unknown"
1822
).toUpperCase();
1923

24+
const getReferrerInfo = (bidderRequest) => {
25+
return bidderRequest?.refererInfo?.page ?? '';
26+
}
27+
2028
const parseNativeAd = function (bid) {
2129
try {
2230
const nativeAd = JSON.parse(bid.ad);
@@ -123,14 +131,50 @@ export const spec = {
123131

124132
buildRequests: function (bidRequests, bidderRequest) {
125133
const userSyncEnabled = config.getConfig("userSync.syncEnabled");
134+
// (!) that avoids top-level side effects (the thing that can stop registerBidder from running)
135+
const computeTTFB = (w = (typeof window !== 'undefined' ? window : undefined)) => {
136+
try {
137+
const wt = (() => { try { return w?.top ?? w; } catch { return w; } })();
138+
const p = wt?.performance || wt?.webkitPerformance || wt?.msPerformance || wt?.mozPerformance;
139+
if (!p) return '';
140+
141+
if (typeof p.getEntriesByType === 'function') {
142+
const nav = p.getEntriesByType('navigation')?.[0];
143+
if (nav?.responseStart > 0 && nav?.requestStart > 0) {
144+
return String(Math.round(nav.responseStart - nav.requestStart));
145+
}
146+
}
147+
148+
const t = p.timing;
149+
if (t?.responseStart > 0 && t?.requestStart > 0) {
150+
return String(t.responseStart - t.requestStart);
151+
}
152+
153+
return '';
154+
} catch {
155+
return '';
156+
}
157+
};
158+
159+
// simple caching
160+
const getTTFBOnce = (() => {
161+
let cached = false;
162+
let done = false;
163+
return () => {
164+
if (done) return cached;
165+
done = true;
166+
cached = computeTTFB();
167+
return cached;
168+
};
169+
})();
126170
const ortbRequest = ORTB.toORTB({ bidderRequest, bidRequests });
127171

128172
if (bidRequests.length === 0) {
129173
return [];
130174
}
131-
const gdpr = bidderRequest.gdprConsent;
132-
const usp = bidderRequest.uspConsent;
133-
const gpp = bidderRequest.gppConsent;
175+
const gdpr = bidderRequest?.gdprConsent;
176+
const usp = bidderRequest?.uspConsent;
177+
const gpp = bidderRequest?.gppConsent;
134178
const hasWallet = detectWalletsPresence();
135179

136180
return bidRequests.map((bidRequest) => {
@@ -139,6 +183,7 @@ export const spec = {
139183
const width = size[0];
140184
const height = size[1];
141185
const originalAssets = bidRequest.mediaTypes?.native?.ortb?.assets || [];
186+
142187
// convert icon to img type 1
143188
const processedAssets = originalAssets.map(asset => {
144189
if (asset.icon) {
@@ -186,6 +231,23 @@ export const spec = {
186231
wdb: hasWallet,
187232
externalRef: bidRequest.bidId,
188233
userSyncOption: userSyncEnabled === false ? "OFF" : "BIDDERS",
234+
referer: getReferrerInfo(bidderRequest),
235+
pageReferer: document.referrer,
236+
pageTitle: getPageTitle().slice(0, 300),
237+
pageDescription: getPageDescription().slice(0, 300),
238+
domComplexity: getDomComplexity(document),
239+
device: bidderRequest?.ortb2?.device || {},
240+
deviceWidth: screen.width,
241+
deviceHeight: screen.height,
242+
timeout: bidderRequest?.timeout,
243+
viewportHeight: utils.getWinDimensions().visualViewport.height,
244+
viewportWidth: utils.getWinDimensions().visualViewport.width,
245+
timeToFirstByte: getTTFBOnce(),
246+
ext: {
247+
...(bidderRequest?.ortb2?.ext || {}),
248+
adapter_version: ADAPTER_VERSION,
249+
prebid_version: PREBID_VERSION
250+
}
189251
};
190252

191253
const wrapperOn =

test/spec/modules/sevioBidAdapter_spec.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,139 @@ describe('sevioBidAdapter', function () {
274274
expect(requests[0].data.keywords).to.have.property('tokens');
275275
expect(requests[0].data.keywords.tokens).to.deep.equal(['keyword1', 'keyword2']);
276276
});
277+
278+
// Minimal env shims some helpers rely on
279+
Object.defineProperty(window, 'visualViewport', {
280+
value: { width: 1200, height: 800 },
281+
configurable: true
282+
});
283+
Object.defineProperty(window, 'screen', {
284+
value: { width: 1920, height: 1080 },
285+
configurable: true
286+
});
287+
288+
function mkBid(overrides) {
289+
return Object.assign({
290+
bidId: 'bid-1',
291+
bidder: 'sevio',
292+
params: { zone: 'zone-123', referenceId: 'ref-abc', keywords: ['k1', 'k2'] },
293+
mediaTypes: { banner: { sizes: [[300, 250]] } },
294+
refererInfo: { page: 'https://example.com/page', referer: 'https://referrer.example' },
295+
userIdAsEids: []
296+
}, overrides || {});
297+
}
298+
299+
const baseBidderRequest = {
300+
timeout: 1200,
301+
refererInfo: { page: 'https://example.com/page', referer: 'https://referrer.example' },
302+
gdprConsent: { consentString: 'TCF-STRING' },
303+
uspConsent: { uspString: '1NYN' },
304+
gppConsent: { consentString: 'GPP-STRING' },
305+
ortb2: { device: {}, ext: {} }
306+
};
307+
308+
describe('Sevio adapter helper coverage via buildRequests (JS)', () => {
309+
let stubs = [];
310+
311+
afterEach(() => {
312+
while (stubs.length) stubs.pop().restore();
313+
document.title = '';
314+
document.head.innerHTML = '';
315+
try {
316+
Object.defineProperty(navigator, 'connection', { value: undefined, configurable: true });
317+
} catch (e) {}
318+
});
319+
320+
it('getReferrerInfo → data.referer', () => {
321+
const out = spec.buildRequests([mkBid()], baseBidderRequest);
322+
expect(out).to.have.lengthOf(1);
323+
expect(out[0].data.referer).to.equal('https://example.com/page');
324+
});
325+
326+
it('getPageTitle prefers top.title; falls back to og:title (top document)', () => {
327+
window.top.document.title = 'Doc Title';
328+
let out = spec.buildRequests([mkBid()], baseBidderRequest);
329+
expect(out[0].data.pageTitle).to.equal('Doc Title');
330+
331+
window.top.document.title = '';
332+
const meta = window.top.document.createElement('meta');
333+
meta.setAttribute('property', 'og:title');
334+
meta.setAttribute('content', 'OG Title');
335+
window.top.document.head.appendChild(meta);
336+
337+
out = spec.buildRequests([mkBid()], baseBidderRequest);
338+
expect(out[0].data.pageTitle).to.equal('OG Title');
339+
340+
meta.remove();
341+
});
342+
343+
it('getPageTitle cross-origin fallback (window.top throws) uses local document.*', function () {
344+
document.title = 'Local Title';
345+
346+
// In jsdom, window.top === window; try to simulate cross-origin by throwing from getter.
347+
let restored = false;
348+
try {
349+
const original = Object.getOwnPropertyDescriptor(window, 'top');
350+
Object.defineProperty(window, 'top', {
351+
configurable: true,
352+
get() { throw new Error('cross-origin'); }
353+
});
354+
const out = spec.buildRequests([mkBid()], baseBidderRequest);
355+
expect(out[0].data.pageTitle).to.equal('Local Title');
356+
Object.defineProperty(window, 'top', original);
357+
restored = true;
358+
} catch (e) {
359+
// Environment didn’t allow redefining window.top; skip this case
360+
this.skip();
361+
} finally {
362+
if (!restored) {
363+
try { Object.defineProperty(window, 'top', { value: window, configurable: true }); } catch (e) {}
364+
}
365+
}
366+
});
367+
368+
it('computeTTFB via navigation entries (top.performance) and cached within call', () => {
369+
const perfTop = window.top.performance;
370+
371+
const original = perfTop.getEntriesByType;
372+
Object.defineProperty(perfTop, 'getEntriesByType', {
373+
configurable: true, writable: true,
374+
value: (type) => (type === 'navigation' ? [{ responseStart: 152, requestStart: 100 }] : [])
375+
});
376+
377+
const out = spec.buildRequests([mkBid({ bidId: 'A' }), mkBid({ bidId: 'B' })], baseBidderRequest);
378+
expect(out).to.have.lengthOf(2);
379+
expect(out[0].data.timeToFirstByte).to.equal('52');
380+
expect(out[1].data.timeToFirstByte).to.equal('52');
381+
382+
Object.defineProperty(perfTop, 'getEntriesByType', { configurable: true, writable: true, value: original });
383+
});
384+
385+
it('computeTTFB falls back to top.performance.timing when no navigation entries', () => {
386+
const perfTop = window.top.performance;
387+
const originalGetEntries = perfTop.getEntriesByType;
388+
const originalTimingDesc = Object.getOwnPropertyDescriptor(perfTop, 'timing');
389+
390+
Object.defineProperty(perfTop, 'getEntriesByType', {
391+
configurable: true, writable: true, value: () => []
392+
});
393+
394+
Object.defineProperty(perfTop, 'timing', {
395+
configurable: true,
396+
value: { responseStart: 250, requestStart: 200 }
397+
});
398+
399+
const out = spec.buildRequests([mkBid()], baseBidderRequest);
400+
expect(out[0].data.timeToFirstByte).to.equal('50');
401+
402+
Object.defineProperty(perfTop, 'getEntriesByType', {
403+
configurable: true, writable: true, value: originalGetEntries
404+
});
405+
if (originalTimingDesc) {
406+
Object.defineProperty(perfTop, 'timing', originalTimingDesc);
407+
} else {
408+
Object.defineProperty(perfTop, 'timing', { configurable: true, value: undefined });
409+
}
410+
});
411+
});
277412
});

0 commit comments

Comments
 (0)