Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 109 additions & 172 deletions modules/hypelabBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,202 +1,139 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER } from '../src/mediaTypes.js';
import { generateUUID, isFn, isPlainObject, getWinDimensions } from '../src/utils.js';
import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js';
import { ajax } from '../src/ajax.js';
import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
import { getWalletPresence, getWalletProviderFlags } from '../libraries/hypelabUtils/hypelabUtils.js';

export const BIDDER_CODE = 'hypelab';
export const ENDPOINT_URL = 'https://api.hypelab.com';

export const REQUEST_ROUTE = '/v1/prebid_requests';
export const EVENT_ROUTE = '/v1/events';
export const REPORTING_ROUTE = '';

const PREBID_VERSION = '$prebid.version$';
const PROVIDER_NAME = 'prebid';
const PROVIDER_VERSION = '0.0.3';

const url = (route) => ENDPOINT_URL + route;

export function mediaSize(data) {
if (!data || !data.creative_set) return { width: 0, height: 0 };
const media = data.creative_set.video || data.creative_set.image || {};
return { width: media.width, height: media.height };
}

function isBidRequestValid(bidderRequest) {
import { registerBidder } from "../src/adapters/bidderFactory.js";
import { BANNER } from "../src/mediaTypes.js";
import {
generateUUID,
getWinDimensions,
mergeDeep,
replaceAuctionPrice,
triggerPixel,
} from "../src/utils.js";
import { getBoundingClientRect } from "../libraries/boundingClientRect/boundingClientRect.js";
import {
getWalletPresence,
getWalletProviderFlags,
} from "../libraries/hypelabUtils/hypelabUtils.js";
import { ortbConverter } from "../libraries/ortbConverter/converter.js";

export const BIDDER_CODE = "hypelab";
export const ENDPOINT_URL = "https://api.hypelab.com/v1/rtb_requests";

const PREBID_VERSION = "$prebid.version$";
const PROVIDER_VERSION = "0.0.4";

const converter = ortbConverter({
context: {
netRevenue: true,
ttl: 360,
mediaType: BANNER,
},
imp(buildImp, bidRequest, context) {
const imp = buildImp(bidRequest, context);
mergeDeep(imp, {
ext: {
bidder: {
property_slug: bidRequest.params.property_slug,
placement_slug: bidRequest.params.placement_slug,
pp: getPosition(bidRequest.adUnitCode),
},
},
});
return imp;
},
request(buildRequest, imps, bidderRequest, context) {
const request = buildRequest(imps, bidderRequest, context);
request.at = 1;
if (!request.cur) request.cur = ["USD"];

const userId = getUserId(context.bidRequests);

mergeDeep(request, {
ext: {
source: "prebid",
sdk_version: PREBID_VERSION,
provider_version: PROVIDER_VERSION,
dpr: typeof window !== "undefined" ? window.devicePixelRatio : 1,

Check warning

Code scanning / CodeQL

Use of browser API associated with fingerprinting Warning

devicePixelRatio is an indicator of fingerprinting; weight: 18.39
vp: getViewport(),
},
user: {
buyeruid: userId,
id: userId,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not overwrite existing ORTB user.id

The ORTB builder already starts from bidderRequest.ortb2, but this assignment unconditionally replaces user.id with the adapter-derived userId (or a generated tmp_* ID). In setups where publishers provide ortb2.user.id but do not provide userIdAsEids, this drops a stable first-party identifier and replaces it with a per-request random value, which can reduce identity match quality and bidding performance; preserve an existing request.user.id and only set buyeruid/fallback IDs when needed.

Useful? React with 👍 / 👎.

ext: {
wids: [],
wp: getWalletPresence(),
wpfs: getWalletProviderFlags(),
},
},
});

return request;
},
});

function isBidRequestValid(bidRequest) {
return (
!!bidderRequest.params?.property_slug &&
!!bidderRequest.params?.placement_slug
!!bidRequest.params?.property_slug && !!bidRequest.params?.placement_slug
);
}

function buildRequests(validBidRequests, bidderRequest) {
const result = validBidRequests.map((request) => {
const uids = (request.userIdAsEids || []).reduce((a, c) => {
const ids = c.uids.map((uid) => uid.id);
return [...a, ...ids];
}, []);

const uuid = uids[0] ? uids[0] : generateTemporaryUUID();
const floor = getBidFloor(request, request.sizes || []);
const dpr = typeof window !== 'undefined' ? getDevicePixelRatio(window) : 1;
const wp = getWalletPresence();
const wpfs = getWalletProviderFlags();
const winDimensions = getWinDimensions();
const vp = [
Math.max(
winDimensions?.document.documentElement.clientWidth || 0,
winDimensions?.innerWidth || 0
),
Math.max(
winDimensions?.document.documentElement.clientHeight || 0,
winDimensions?.innerHeight || 0
),
];
const pp = getPosition(request.adUnitCode);

const payload = {
property_slug: request.params.property_slug,
placement_slug: request.params.placement_slug,
provider_version: PROVIDER_VERSION,
provider_name: PROVIDER_NAME,
location:
bidderRequest.refererInfo?.page || typeof window !== 'undefined'
? window.location.href
: '',
sdk_version: PREBID_VERSION,
sizes: request.sizes,
wids: [],
floor,
dpr,
uuid,
bidRequestsCount: request.bidRequestsCount,
bidderRequestsCount: request.bidderRequestsCount,
bidderWinsCount: request.bidderWinsCount,
wp,
wpfs,
vp,
pp,
};

return {
method: 'POST',
url: url(REQUEST_ROUTE),
options: { contentType: 'application/json', withCredentials: true },
data: payload,
bidId: request.bidId,
};
});

return result;
}

function generateTemporaryUUID() {
return 'tmp_' + generateUUID();
function buildRequests(bidRequests, bidderRequest) {
return bidRequests.map((bidRequest) => ({
method: "POST",
url: ENDPOINT_URL,
data: converter.toORTB({ bidRequests: [bidRequest], bidderRequest }),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve params.bidFloor fallback in ORTB requests

By routing request construction entirely through converter.toORTB(...), the adapter no longer carries forward the previous fallback that read bid.params.bidFloor when no getFloor() function was present. Since ORTB bidfloor population depends on getFloor (typically from the floors module), deployments that rely on static params.bidFloor without that module will stop signaling floors after this migration, allowing bids below the intended minimum.

Useful? React with 👍 / 👎.

options: { contentType: "application/json", withCredentials: true },

Check warning

Code scanning / CodeQL

Application/json request type in bidder Warning

application/json request type triggers preflight requests and may increase bidder timeouts
}));
}

function getBidFloor(bid, sizes) {
if (!isFn(bid.getFloor)) {
return bid.params.bidFloor ? bid.params.bidFloor : null;
}

let floor;

const floorInfo = bid.getFloor({
currency: 'USD',
mediaType: 'banner',
size: sizes.length === 1 ? sizes[0] : '*',
function interpretResponse(response, request) {
if (!response.body) return [];
const result = converter.fromORTB({
request: request.data,
response: response.body,
});
return result.bids || [];
}

if (
isPlainObject(floorInfo) &&
floorInfo.currency === 'USD' &&
!isNaN(parseFloat(floorInfo.floor))
) {
floor = parseFloat(floorInfo.floor);
function onBidWon(bid) {
if (bid.burl) {
triggerPixel(replaceAuctionPrice(bid.burl, bid.originalCpm || bid.cpm));
}

return floor;
}

function getPosition(id) {
const element = document.getElementById(id);
function getPosition(adUnitCode) {
const element = document.getElementById(adUnitCode);
if (!element) return null;
const rect = getBoundingClientRect(element);
return [rect.left, rect.top];
}

function interpretResponse(serverResponse, bidRequest) {
const { data } = serverResponse.body;

if (!data.cpm || !data.html) return [];

const size = mediaSize(data);

const result = {
requestId: bidRequest.bidId,
cpm: data.cpm,
width: size.width,
height: size.height,
creativeId: data.creative_set_slug,
currency: data.currency,
netRevenue: true,
referrer: bidRequest.data.location,
ttl: data.ttl,
ad: data.html,
mediaType: serverResponse.body.data.media_type,
meta: {
advertiserDomains: data.advertiser_domains || [],
},
};

return [result];
}

export function report(eventType, data, route = REPORTING_ROUTE) {
if (!route) return;

const options = {
method: 'POST',
contentType: 'application/json',
withCredentials: true,
};

const request = { type: eventType, data };
ajax(url(route), null, request, options);
}

function onTimeout(timeoutData) {
this.report('timeout', timeoutData);
}

function onBidWon(bid) {
this.report('bidWon', bid);
}

function onSetTargeting(bid) {
this.report('setTargeting', bid);
function getUserId(bidRequests) {
const eids = bidRequests?.[0]?.userIdAsEids || [];
const uids = eids.flatMap((eid) => eid.uids.map((uid) => uid.id));
return uids[0] || "tmp_" + generateUUID();
}

function onBidderError(errorData) {
this.report('bidderError', errorData);
function getViewport() {
const win = getWinDimensions();
return [
Math.max(
win?.document.documentElement.clientWidth || 0,
win?.innerWidth || 0
),
Math.max(
win?.document.documentElement.clientHeight || 0,
win?.innerHeight || 0
),
];
}

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],
aliases: ['hype'],
aliases: ["hype"],
isBidRequestValid,
buildRequests,
interpretResponse,
onTimeout,
onBidWon,
onSetTargeting,
onBidderError,
report,
REPORTING_ROUTE: 'a',
};

registerBidder(spec);
Loading
Loading