Skip to content

Commit

Permalink
BMS Bid Adapter : initial release (#12621)
Browse files Browse the repository at this point in the history
* wip

* chore: update ENDPOINT_URL

* chore: update permission for localstorage

* feat(bmsBidAdapter): implement bid floor logic and update request structure

* test(bmsBidAdapter): remove commented-out tests for interpretResponse

* wip

* wip

* Refactor geolocation implementationn

* chore: minor adjustments

* feat: add bidWon

* update test

* chore: Change double quotes to single quotes

* Update creativeId and creative_id values

* refactor: remove unused cookie ID handling from bid request

* wip

* Remove deprecated BMS sample HTML and update BMS bid adapter to use JSON.stringify for requests and sendBeacon for bid won notifications

* wip

* Update creative.html

* Update creative.html

---------

Co-authored-by: Patrick McCann <[email protected]>
  • Loading branch information
iagoBMS and patmmccann authored Feb 24, 2025
1 parent f86d267 commit 9079ca8
Show file tree
Hide file tree
Showing 3 changed files with 371 additions and 0 deletions.
158 changes: 158 additions & 0 deletions modules/bmsBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { sendBeacon } from '../src/ajax.js';
import { BANNER } from '../src/mediaTypes.js';
import { getStorageManager } from '../src/storageManager.js';
import {
replaceAuctionPrice,
isFn,
isPlainObject,
deepSetValue,
isEmpty,
} from '../src/utils.js';
const BIDDER_CODE = 'bms';
const ENDPOINT_URL =
'https://api.prebid.int.us-east-1.bluems.com/v1/bid?exchangeId=prebid';
const GVLID = 1105;
const DEFAULT_CURRENCY = 'USD';
const DEFAULT_BID_TTL = 1200;

export const storage = getStorageManager({ bidderCode: BIDDER_CODE });

function getBidFloor(bid) {
if (isFn(bid.getFloor)) {
let floor = bid.getFloor({
currency: DEFAULT_CURRENCY,
mediaType: BANNER,
size: '*',
});
if (
isPlainObject(floor) &&
!isNaN(floor.floor) &&
floor.currency === DEFAULT_CURRENCY
) {
return floor.floor;
}
}
return null;
}

const converter = ortbConverter({
context: {
netRevenue: true, // Default net revenue configuration
ttl: 100, // Default time-to-live for bid responses
},
imp,
request,
});

function request(buildRequest, imps, bidderRequest, context) {
let request = buildRequest(imps, bidderRequest, context);

// Add publisher ID
deepSetValue(request, 'site.publisher.id', context.publisherId);
return request;
}

function imp(buildImp, bidRequest, context) {
let imp = buildImp(bidRequest, context);
const floor = getBidFloor(bidRequest);
imp.tagid = bidRequest.params.placementId;

if (floor) {
imp.bidfloor = floor;
imp.bidfloorcur = DEFAULT_CURRENCY;
}

return imp;
}

export const spec = {
code: BIDDER_CODE,
gvlid: GVLID,
supportedMediaTypes: [BANNER],

// Validate bid request
isBidRequestValid: function (bid) {
return !!bid.params.placementId && !!bid.params.publisherId;
},

// Build OpenRTB requests using `ortbConverter`
buildRequests: function (validBidRequests, bidderRequest) {
const context = {
publisherId: validBidRequests.find(
(bidRequest) => bidRequest.params?.publisherId
)?.params.publisherId,
};

const ortbRequest = converter.toORTB({
bidRequests: validBidRequests,
bidderRequest,
context,
});

// Add extensions to the request
ortbRequest.ext = ortbRequest.ext || {};
deepSetValue(ortbRequest, 'ext.gvlid', GVLID);

return [
{
method: 'POST',
url: ENDPOINT_URL,
data: JSON.stringify(ortbRequest),
options: {
contentType: 'text/plain',
withCredentials: false,
},
},
];
},

interpretResponse: (serverResponse) => {
if (!serverResponse || isEmpty(serverResponse.body)) return [];

let bids = [];
serverResponse.body.seatbid.forEach((response) => {
response.bid.forEach((bid) => {
const mediaType = bid.ext?.mediaType || 'banner';
bids.push({
ad: replaceAuctionPrice(bid.adm, bid.price),
adapterCode: BIDDER_CODE,
cpm: bid.price,
creativeId: bid.ext.bms.adId,
currency: serverResponse.body.cur || 'USD',
deferBilling: false,
deferRendering: false,
width: bid.w,
height: bid.h,
mediaType,
netRevenue: true,
originalCpm: bid.price,
originalCurrency: serverResponse.body.cur || 'USD',
requestId: bid.impid,
seatBidId: bid.id,
ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL,
meta: {
advertiserDomains: bid.adomain || [],
networkId: bid.ext?.networkId || 1105,
networkName: bid.ext?.networkName || 'BMS',
}
});
});
});
return bids;
},

onBidWon: function (bid) {
const { burl, nurl } = bid || {};
if (nurl) {
sendBeacon(nurl);
}

if (burl) {
sendBeacon(burl);
}
},
};

registerBidder(spec);
48 changes: 48 additions & 0 deletions modules/bmsBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Overview

Module Name: bms Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]

# Description

Module that connects to bms's demand sources.

# Test Parameters

```
var adUnits = [
{
code: "test-div",
mediaTypes: {
banner: {
sizes: [
[300, 250],
[300, 600],
],
},
},
floors: {
currency: "USD",
schema: {
delimiter: "|",
fields: ["mediaType", "size"],
},
values: {
"banner|300x250": 1.1,
"banner|300x600": 1.35,
"banner|*": 2,
},
},
bids: [
{
bidder: "bms",
params: {
placementId: 13144370,
publisherId: 13144370,
},
},
],
},
];
```
165 changes: 165 additions & 0 deletions test/spec/modules/bmsBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { expect } from 'chai';
import sinon from 'sinon';
import { spec, storage } from 'modules/bmsBidAdapter.js';

const BIDDER_CODE = 'bms';
const ENDPOINT_URL =
'https://api.prebid.int.us-east-1.bluems.com/v1/bid?exchangeId=prebid';
const GVLID = 1105;
const CURRENCY = 'USD';

describe('bmsBidAdapter:', function () {
let sandbox;

beforeEach(function () {
sandbox = sinon.createSandbox();
});

afterEach(function () {
sandbox.restore();
});

describe('isBidRequestValid:', function () {
it('should return true for valid bid requests', function () {
const validBid = {
params: {
placementId: '12345',
publisherId: '67890',
},
};
expect(spec.isBidRequestValid(validBid)).to.be.true;
});

it('should return false for invalid bid requests', function () {
const invalidBid = {
params: {
placementId: '12345',
},
};
expect(spec.isBidRequestValid(invalidBid)).to.be.false;
});
});

describe('buildRequests:', function () {
let validBidRequests;
let bidderRequest;

beforeEach(function () {
validBidRequests = [
{
bidId: 'bid1',
params: {
placementId: '12345',
publisherId: '67890',
},
getFloor: () => ({ currency: CURRENCY, floor: 1.5 }),
},
];

bidderRequest = {
refererInfo: {
page: 'https://example.com',
},
};

sandbox.stub(storage, 'getDataFromLocalStorage').returns('testBuyerId');
});

it('should build a valid OpenRTB request', function () {
const [request] = spec.buildRequests(validBidRequests, bidderRequest);
expect(request.method).to.equal('POST');
expect(request.url).to.equal(ENDPOINT_URL);
expect(request.options.contentType).to.equal('text/plain');
const ortbRequest = JSON.parse(request.data);

expect(ortbRequest.ext.gvlid).to.equal(GVLID);
expect(ortbRequest.imp[0].bidfloor).to.equal(1.5);
expect(ortbRequest.imp[0].bidfloorcur).to.equal(CURRENCY);
});

it('should omit bidfloor if getFloor is not implemented', function () {
validBidRequests[0].getFloor = undefined;

const [request] = spec.buildRequests(validBidRequests, bidderRequest);
const ortbRequest = JSON.parse(request.data);

expect(ortbRequest.imp[0].bidfloor).to.be.undefined;
});

it('should convert from fromORTB', function () {
const response = {
id: 'response-id-123456',
cur: 'USD',
bidid: '2rgRKcbHfDyX6ZU4zuPuf38h000',
seatbid: [
{
bid: [
{
id: '2rgRKcbHfDyX6ZU4zuPuf521444:0',
impid: '3b948a96652621',
price: 2,
adomain: ['example.com'],
adid: '0',
adm: '<iframe src="https://ads.example.com/ad-content?acc=account-123&ad=ad-123&bid=bid-id-abcdef:0&imp=impression-id-12345" height="600" width="300" marginwidth="0" marginheight="0" align="top" scrolling="No" frameborder="0" hspace="0" vspace="0"></iframe>',
iurl: 'https://ads.bluemsusercontent.com/v1/ad-container?acc=306850905425&ad=2pMGbaJioMDwMIESvwlCUekrdNA',
h: 600,
w: 300,
nurl: 'https://bid-notice.rtb.bluems.com/v1/bid:won?winPrice=${AUCTION_PRICE}&accountId=306850905425&adId=2pMGbaJioMDwMIESvwlCUekrdNA&campaignId=2Xzb0pyfcOibtp9A5XsUSkzj2IT&exchangeId=prebid&tagId=13144370&impressionId=3b948a96652621&bidId=2rgRKcbHfDyX6ZU4zuPuf38hDB8%3A0&bidPrice=2&bidFloor=2&height=600&width=300&region=us-east-1&targetId=2rgH24MVckzSou4IpTyUa5l3Ija&currency=USD&campaignCurrency=USD&campaignCurrencyConversionFactor=1&publisherId=13144370&domain=d3fef36u6k6muh.cloudfront.net',
lurl: 'https://bid-notice.rtb.bluems.com/v1/bid:lost?winPrice=${AUCTION_PRICE}&marketBidRatio=${AUCTION_MBR}&lossReasonCode=${AUCTION_LOSS}&accountId=306850905425&adId=2pMGbaJioMDwMIESvwlCUekrdNA&campaignId=2Xzb0pyfcOibtp9A5XsUSkzj2IT&exchangeId=prebid&tagId=13144370&impressionId=3b948a96652621&bidId=2rgRKcbHfDyX6ZU4zuPuf38hDB8%3A0&bidPrice=2&bidFloor=2&height=600&width=300&region=us-east-1&targetId=2rgH24MVckzSou4IpTyUa5l3Ija&currency=USD&campaignCurrency=USD&campaignCurrencyConversionFactor=1&publisherId=13144370&domain=d3fef36u6k6muh.cloudfront.net',
burl: 'https://bid-notice.rtb.bluems.com/v1/bid:charged?winPrice=${AUCTION_PRICE}&accountId=306850905425&adId=2pMGbaJioMDwMIESvwlCUekrdNA&campaignId=2Xzb0pyfcOibtp9A5XsUSkzj2IT&exchangeId=prebid&tagId=13144370&impressionId=3b948a96652621&bidId=2rgRKcbHfDyX6ZU4zuPuf38hDB8%3A0&bidPrice=2&bidFloor=2&height=600&width=300&region=us-east-1&targetId=2rgH24MVckzSou4IpTyUa5l3Ija&currency=USD&campaignCurrency=USD&campaignCurrencyConversionFactor=1&publisherId=13144370&domain=d3fef36u6k6muh.cloudfront.net',
exp: 60,
ext: {
bms: {
accountId: '306850900000',
campaignId: '2Xzb0pyfcOibtp9A5X8546254528',
adId: '2pMGbaJioMDwMIESvwlCUlkojug',
region: 'us-east-1',
targetId: '2rgH24MVckzSou4IpTyUalakush',
},
},
},
],
seat: '1',
},
],
};
const request = {
id: '10bb57ee-712f-43e9-9769-b26d03lklkih',
bidder: BIDDER_CODE,
params: {
source: 886409,
},
mediaTypes: {
banner: {
sizes: [
[300, 250],
[300, 600],
],
},
},
adUnitCode: 'div-gpt-ad-1460505iosakju-0',
transactionId: '7d79850b-70aa-4c0f-af95-c1d524sskjkjh',
sizes: [
[300, 250],
[300, 600],
],
bidId: '2eb89f0f062afe',
bidderRequestId: '1ae6c8e18f8462',
auctionId: '1286637c-51bc-4fdd-8e35-2435elklklka',
ortb2: {},
};

const [ortbReq] = spec.buildRequests([request], {
bids: [request],
});

const ortbResponse = spec.interpretResponse(
{ body: response },
{ data: ortbReq.data }
);

expect(ortbResponse.length).to.eq(1);
expect(ortbResponse[0].mediaType).to.eq('banner');
});
});
});

0 comments on commit 9079ca8

Please sign in to comment.