Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobkoi Bidder Adapter: Initial Release #12647

Merged
merged 4 commits into from
Feb 19, 2025
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat: max-970: Prebid.js Bidder Adapter: Retrieve Adapter Parameters …
…from Bid Configuration Object (#8)

Configuration Object](https://mobkoi.atlassian.net/browse/MAX-970)

At this stage, we are only focused on bid win events, so there is no
need for analytics adapter integration yet. To streamline the
publisher's configuration for our custom bid adapter integration, we
retrieve adapter parameters directly from the bid configuration object
instead of using "bidderConfiguration."

updated bid adapter doc
  • Loading branch information
zeeye committed Jan 27, 2025
commit ee74b1efa2e03763b7ee24a522613e797ebf6971
77 changes: 63 additions & 14 deletions modules/mobkoiBidAdapter.js
Original file line number Diff line number Diff line change
@@ -4,11 +4,17 @@ import { BANNER } from '../src/mediaTypes.js';
import { _each, replaceMacros, deepAccess, deepSetValue, logError } from '../src/utils.js';

const BIDDER_CODE = 'mobkoi';
/**
* !IMPORTANT: This value must match the value in mobkoiAnalyticsAdapter.js
* The name of the parameter that the publisher can use to specify the ad server endpoint.
*/
const PARAM_NAME_AD_SERVER_BASE_URL = 'adServerBaseUrl';
const GVL_ID = 898;

const PUBLISHER_PARAMS = {
/**
* !IMPORTANT: This value must match the value in mobkoiAnalyticsAdapter.js
* The name of the parameter that the publisher can use to specify the ad server endpoint.
*/
PARAM_NAME_AD_SERVER_BASE_URL: 'adServerBaseUrl',
PARAM_NAME_PUBLISHER_ID: 'publisherId',
}

/**
* The list of ORTB response fields that are used in the macros. Field
* replacement is self-implemented in the adapter. Use dot-notated path for
@@ -27,6 +33,8 @@ export const converter = ortbConverter({
const prebidBidRequest = context.bidRequests[0];

ortbRequest.id = utils.getOrtbId(prebidBidRequest);
deepSetValue(ortbRequest, 'site.publisher.id', utils.getPublisherId(prebidBidRequest));
deepSetValue(ortbRequest, 'site.publisher.ext.adServerBaseUrl', utils.getAdServerEndpointBaseUrl(prebidBidRequest));

return ortbRequest;
},
@@ -42,18 +50,37 @@ export const converter = ortbConverter({
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],
gvlid: GVL_ID,

/**
* Determines whether or not the given bid request is valid.
*/
isBidRequestValid(bid) {
if (!deepAccess(bid, 'ortb2.site.publisher.id')) {
logError('The "ortb2.site.publisher.id" field is required in the bid request.' +
'Please set it via the "config.ortb2.site.publisher.id" field with pbjs.setBidderConfig.'
if (
!deepAccess(bid, `params.${PUBLISHER_PARAMS.PARAM_NAME_PUBLISHER_ID}`) &&
!deepAccess(bid, 'ortb2.site.publisher.id')
) {
logError(`The ${PUBLISHER_PARAMS.PARAM_NAME_PUBLISHER_ID} field is required in the bid request. ` +
'Please follow the setup guideline to set the publisher ID field.'
);
return false;
}

if (
!deepAccess(bid, `params.${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}`) &&
!deepAccess(bid, 'ortb2.site.publisher.ext.adServerBaseUrl')) {
logError(
`The "${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}" field is required in the bid request. ` +
'Please follow the setup guideline to set the field.'
);
return false;
}

return true;
},

/**
* Make a server request from the list of BidRequests.
*/
buildRequests(prebidBidRequests, prebidBidderRequest) {
const adServerEndpoint = utils.getAdServerEndpointBaseUrl(prebidBidderRequest) + '/bid';

@@ -69,7 +96,9 @@ export const spec = {
}),
};
},

/**
* Unpack the response from the server into a list of bids.
*/
interpretResponse(serverResponse, customBidRequest) {
if (!serverResponse.body) return [];

@@ -85,7 +114,6 @@ export const spec = {
registerBidder(spec);

export const utils = {

/**
* !IMPORTANT: Make sure the implementation of this function matches getAdServerEndpointBaseUrl
* in both adapters.
@@ -96,16 +124,26 @@ export const utils = {
* @throws {Error} If the ORTB ID cannot be found in the given
*/
getAdServerEndpointBaseUrl (bid) {
const ortbPath = `site.publisher.ext.${PARAM_NAME_AD_SERVER_BASE_URL}`;
// (begin) Fields that would be automatically set if the publisher set it via pbjs.setBidderConfig.
const ortbPath = `site.publisher.ext.${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}`;
const prebidPath = `ortb2.${ortbPath}`;
// (end)

// (begin) Fields that would be set by the publisher in the bid
// configuration object in ad unit.
const paramPath = `params.${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}`;
const bidRequestFirstBidParam = `bids.0.${paramPath}`;
// (end)

const adServerBaseUrl =
deepAccess(bid, paramPath) ||
deepAccess(bid, bidRequestFirstBidParam) ||
deepAccess(bid, prebidPath) ||
deepAccess(bid, ortbPath);

if (!adServerBaseUrl) {
throw new Error('Failed to find the Ad Server Base URL in the given object. ' +
`Please set it via the "${prebidPath}" field with pbjs.setBidderConfig.\n` +
`Please follow the setup documentation to set "${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}".\n` +
'Given Object:\n' +
JSON.stringify(bid, null, 2)
);
@@ -123,10 +161,21 @@ export const utils = {
* @throws {Error} If the publisher ID is not found in the given object.
*/
getPublisherId: function (prebidBidRequestOrOrtbBidRequest) {
// (begin) Fields that would be automatically set if the publisher set it
// via pbjs.setBidderConfig.
const ortbPath = 'site.publisher.id';
const prebidPath = `ortb2.${ortbPath}`;
// (end)

// (begin) Fields that would be set by the publisher in the bid
// configuration object in ad unit.
const paramPath = 'params.publisherId';
const bidRequestFirstBidParam = `bids.0.${paramPath}`;
// (end)

const publisherId =
deepAccess(prebidBidRequestOrOrtbBidRequest, paramPath) ||
deepAccess(prebidBidRequestOrOrtbBidRequest, bidRequestFirstBidParam) ||
deepAccess(prebidBidRequestOrOrtbBidRequest, prebidPath) ||
deepAccess(prebidBidRequestOrOrtbBidRequest, ortbPath);

@@ -135,7 +184,7 @@ export const utils = {
'Failed to obtain publisher ID from the given object. ' +
`Please set it via the "${prebidPath}" field with pbjs.setBidderConfig.\n` +
'Given object:\n' +
JSON.stringify(prebidBidRequestOrOrtbBidRequest, null, 2)
JSON.stringify({functionParam: prebidBidRequestOrOrtbBidRequest}, null, 3)
);
}

20 changes: 4 additions & 16 deletions modules/mobkoiBidAdapter.md
Original file line number Diff line number Diff line change
@@ -22,28 +22,16 @@ const adUnits = [
bids: [
{
bidder: 'mobkoi',
params: {
publisherId: 'module-test-publisher-id',
adServerBaseUrl: 'https://adserver.maximus.mobkoi.com',
}
},
],
},
];

pbjs.que.push(function () {
pbjs.setBidderConfig({
bidders: ['mobkoi'],
config: {
ortb2: {
site: {
publisher: {
id: 'module-test-publisher-id',
ext: {
adServerBaseUrl: 'https://adserver.dev.mobkoi.com',
},
},
},
},
},
});

pbjs.addAdUnits(adUnits);
});
```
110 changes: 77 additions & 33 deletions test/spec/modules/mobkoiBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,51 @@
import {spec, utils} from 'modules/mobkoiBidAdapter.js';

describe('Mobkoi bidding Adapter', function () {
const adServerBaseUrl = 'http://adServerBaseUrl';
const requestId = 'test-request-id'
const publisherId = 'mobkoiPublisherId'
const bidId = 'test-bid-id'
const testAdServerBaseUrl = 'http://test.adServerBaseUrl.com';
const testRequestId = 'test-request-id'
const testPublisherId = 'mobkoiPublisherId'
const testBidId = 'test-bid-id'
const bidderCode = 'mobkoi'
const transactionId = 'test-transaction-id'
const adUnitId = 'test-ad-unit-id'
const auctionId = 'test-auction-id'
const testTransactionId = 'test-transaction-id'
const testAdUnitId = 'test-ad-unit-id'
const testAuctionId = 'test-auction-id'

const getOrtb2 = () => ({
site: {
publisher: {
id: publisherId,
ext: { adServerBaseUrl }
id: testPublisherId,
ext: { adServerBaseUrl: testAdServerBaseUrl }
}
}
})

const getBidRequest = () => ({
bidder: bidderCode,
adUnitCode: 'banner-ad',
transactionId,
adUnitId,
bidId: bidId,
bidderRequestId: requestId,
auctionId,
ortb2: getOrtb2()
transactionId: testTransactionId,
adUnitId: testAdUnitId,
bidId: testBidId,
bidderRequestId: testRequestId,
auctionId: testAuctionId,
ortb2: getOrtb2(),
params: {
publisherId: testPublisherId,
adServerBaseUrl: testAdServerBaseUrl
}
})

const getBidderRequest = () => ({
bidderCode,
auctionId,
bidderRequestId: requestId,
auctionId: testAuctionId,
bidderRequestId: testRequestId,
bids: [getBidRequest()],
ortb2: getOrtb2()
})

const getConvertedBidRequest = () => ({
id: requestId,
cur: [
'USD'
],
id: testRequestId,
imp: [{
id: bidId,
id: testBidId,
}],
...getOrtb2(),
test: 0
@@ -56,15 +57,15 @@ describe('Mobkoi bidding Adapter', function () {

const getBidderResponse = () => ({
body: {
id: bidId,
id: testBidId,
cur: 'USD',
seatbid: [
{
seat: 'mobkoi_debug',
bid: [
{
id: bidId,
impid: bidId,
id: testBidId,
impid: testBidId,
cid: 'campaign_1',
crid: 'creative_1',
price: 1,
@@ -94,17 +95,41 @@ describe('Mobkoi bidding Adapter', function () {
bid = getBidderRequest().bids[0];
});

it('should return true when publisher id exists in ortb2', function () {
it('should return true when publisher id only exists in ortb2', function () {
delete bid.params.publisherId;
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return true when publisher ID only exists in ad unit params', function () {
delete bid.ortb2.site.publisher.id;
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return true when adServerBaseUrl only exists in ortb2', function () {
delete bid.params.adServerBaseUrl;
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false when publisher id is missing', function () {
it('should return true when adServerBaseUrl only exists in ad unit params', function () {
delete bid.ortb2.site.publisher.ext.adServerBaseUrl;
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false when publisher id is missing both in ad unit params and ortb2', function () {
delete bid.ortb2.site.publisher.id;
delete bid.params.publisherId;
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false when publisher id is empty', function () {
it('should return false when publisher id is empty in ad unit params and ortb2', function () {
bid.ortb2.site.publisher.id = '';
bid.params.publisherId = '';
expect(spec.isBidRequestValid(bid)).to.equal(false);
});

it('should return false when adServerBaseUrl is missing in ad unit params and ortb2', function () {
delete bid.ortb2.site.publisher.ext.adServerBaseUrl;
delete bid.params.adServerBaseUrl;
expect(spec.isBidRequestValid(bid)).to.equal(false);
});
})
@@ -119,7 +144,7 @@ describe('Mobkoi bidding Adapter', function () {

it('should return valid request object with correct structure', function () {
const request = spec.buildRequests(bidderRequest.bids, bidderRequest);
const expectedUrl = adServerBaseUrl + '/bid';
const expectedUrl = testAdServerBaseUrl + '/bid';

expect(request.method).to.equal('POST');
expect(request.options.contentType).to.equal('application/json');
@@ -135,14 +160,31 @@ describe('Mobkoi bidding Adapter', function () {
expect(ortbData.site.publisher.id).to.equal(bidderRequest.ortb2.site.publisher.id);
});

it('should throw error when adServerBaseUrl is missing', function () {
it('should obtain publisher ID from ad unit params if the value does not exist in ortb2.', function () {
delete bidderRequest.ortb2.site.publisher.id;
const request = spec.buildRequests(bidderRequest.bids, bidderRequest);
const ortbData = request.data;

expect(ortbData.site.publisher.id).to.equal(bidderRequest.bids[0].params.publisherId);
});

it('should obtain adServerBaseUrl from ad unit params if the value does not exist in ortb2.', function () {
delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl;
const request = spec.buildRequests(bidderRequest.bids, bidderRequest);
const ortbData = request.data;

expect(ortbData.site.publisher.ext.adServerBaseUrl).to.equal(bidderRequest.bids[0].params.adServerBaseUrl);
});

it('should throw error when adServerBaseUrl is missing both in ortb2 and bid params', function () {
delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl;
delete bidderRequest.bids[0].params.adServerBaseUrl;

expect(() => {
spec.buildRequests(bidderRequest.bids, bidderRequest);
}).to.throw();
});
})
});

describe('interpretResponse', function () {
let bidderRequest, bidRequest, bidderResponse;
@@ -184,11 +226,12 @@ describe('Mobkoi bidding Adapter', function () {
describe('getAdServerEndpointBaseUrl', function () {
it('should return the adServerBaseUrl from the given object', function () {
expect(utils.getAdServerEndpointBaseUrl(bidderRequest))
.to.equal(adServerBaseUrl);
.to.equal(testAdServerBaseUrl);
});

it('should throw error when adServerBaseUrl is missing', function () {
delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl;
delete bidderRequest.bids[0].params.adServerBaseUrl;

expect(() => {
utils.getAdServerEndpointBaseUrl(bidderRequest);
@@ -203,6 +246,7 @@ describe('Mobkoi bidding Adapter', function () {

it('should throw error when publisherId is missing', function () {
delete bidderRequest.ortb2.site.publisher.id;
delete bidderRequest.bids[0].params.publisherId;
expect(() => {
utils.getPublisherId(bidderRequest);
}).to.throw();
@@ -255,7 +299,7 @@ describe('Mobkoi bidding Adapter', function () {
bid.lurl = '${BIDDING_API_BASE_URL}/loss?price=${AUCTION_PRICE}&impressionId=${AUCTION_IMP_ID}&currency=${AUCTION_CURRENCY}&campaignId=${CAMPAIGN_ID}&creativeId=${CREATIVE_ID}&publisherId=${PUBLISHER_ID}&ortbId=${ORTB_ID}';
bid.adm = '<div>${AUCTION_PRICE}${AUCTION_CURRENCY}${AUCTION_IMP_ID}${AUCTION_BID_ID}${CAMPAIGN_ID}${CREATIVE_ID}${PUBLISHER_ID}${ORTB_ID}${BIDDING_API_BASE_URL}</div>';

const BIDDING_API_BASE_URL = adServerBaseUrl;
const BIDDING_API_BASE_URL = testAdServerBaseUrl;
const AUCTION_CURRENCY = bidderResponse.body.cur;
const AUCTION_BID_ID = bidderRequest.auctionId;
const AUCTION_PRICE = bid.price;
Loading
Oops, something went wrong.