forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Holid Bid Adapter: initial release (prebid#9371)
* Holid bid adapter * Adjust test to various device sizes * Include first party data from ortb2 object * Remove trailing spaces in test
- Loading branch information
1 parent
2a862fc
commit 1c5d0fe
Showing
3 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { | ||
deepAccess, | ||
getBidIdParameter, | ||
isStr, | ||
logMessage, | ||
triggerPixel, | ||
} from '../src/utils.js' | ||
import * as events from '../src/events.js' | ||
import CONSTANTS from '../src/constants.json' | ||
import { BANNER } from '../src/mediaTypes.js' | ||
|
||
import { registerBidder } from '../src/adapters/bidderFactory.js' | ||
|
||
const BIDDER_CODE = 'holid' | ||
const GVLID = 1177 | ||
const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction' | ||
const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html' | ||
const TIME_TO_LIVE = 300 | ||
let wurlMap = {} | ||
|
||
events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler) | ||
|
||
export const spec = { | ||
code: BIDDER_CODE, | ||
gvlid: GVLID, | ||
supportedMediaTypes: [BANNER], | ||
|
||
isBidRequestValid: function (bid) { | ||
return !!bid.params.adUnitID | ||
}, | ||
|
||
buildRequests: function (validBidRequests, _bidderRequest) { | ||
return validBidRequests.map((bid) => { | ||
const requestData = { | ||
...bid.ortb2, | ||
id: bid.auctionId, | ||
imp: [getImp(bid)], | ||
} | ||
|
||
return { | ||
method: 'POST', | ||
url: ENDPOINT, | ||
data: JSON.stringify(requestData), | ||
bidId: bid.bidId, | ||
} | ||
}) | ||
}, | ||
|
||
interpretResponse: function (serverResponse, bidRequest) { | ||
const bidResponses = [] | ||
|
||
if (!serverResponse.body.seatbid) { | ||
return [] | ||
} | ||
|
||
serverResponse.body.seatbid.map((response) => { | ||
response.bid.map((bid) => { | ||
const requestId = bidRequest.bidId | ||
const auctionId = bidRequest.auctionId | ||
const wurl = deepAccess(bid, 'ext.prebid.events.win') | ||
const bidResponse = { | ||
requestId, | ||
cpm: bid.price, | ||
width: bid.w, | ||
height: bid.h, | ||
ad: bid.adm, | ||
creativeId: bid.crid, | ||
currency: serverResponse.body.cur, | ||
netRevenue: true, | ||
ttl: TIME_TO_LIVE, | ||
} | ||
|
||
addWurl({ auctionId, requestId, wurl }) | ||
|
||
bidResponses.push(bidResponse) | ||
}) | ||
}) | ||
|
||
return bidResponses | ||
}, | ||
|
||
getUserSyncs(optionsType, serverResponse, gdprConsent, uspConsent) { | ||
if (!serverResponse || serverResponse.length === 0) { | ||
return [] | ||
} | ||
|
||
const syncs = [] | ||
|
||
if (optionsType.iframeEnabled) { | ||
const queryParams = [] | ||
|
||
queryParams.push('bidders=' + getBidders(serverResponse)) | ||
queryParams.push('gdpr=' + +gdprConsent.gdprApplies) | ||
queryParams.push('gdpr_consent=' + gdprConsent.consentString) | ||
queryParams.push('usp_consent=' + (uspConsent || '')) | ||
|
||
let strQueryParams = queryParams.join('&') | ||
|
||
if (strQueryParams.length > 0) { | ||
strQueryParams = '?' + strQueryParams | ||
} | ||
|
||
syncs.push({ | ||
type: 'iframe', | ||
url: COOKIE_SYNC_ENDPOINT + strQueryParams + '&type=iframe', | ||
}) | ||
|
||
return syncs | ||
} | ||
}, | ||
} | ||
|
||
function getImp(bid) { | ||
const imp = { | ||
ext: { | ||
prebid: { | ||
storedrequest: { | ||
id: getBidIdParameter('adUnitID', bid.params), | ||
}, | ||
}, | ||
}, | ||
} | ||
const sizes = | ||
bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes | ||
|
||
if (deepAccess(bid, 'mediaTypes.banner')) { | ||
imp.banner = { | ||
format: sizes.map((size) => { | ||
return { w: size[0], h: size[1] } | ||
}), | ||
} | ||
} | ||
|
||
return imp | ||
} | ||
|
||
function getBidders(serverResponse) { | ||
const bidders = serverResponse | ||
.map((res) => Object.keys(res.body.ext.responsetimemillis)) | ||
.flat(1) | ||
|
||
return encodeURIComponent(JSON.stringify([...new Set(bidders)])) | ||
} | ||
|
||
function addWurl(auctionId, adId, wurl) { | ||
if ([auctionId, adId].every(isStr)) { | ||
wurlMap[`${auctionId}${adId}`] = wurl | ||
} | ||
} | ||
|
||
function removeWurl(auctionId, adId) { | ||
delete wurlMap[`${auctionId}${adId}`] | ||
} | ||
|
||
function getWurl(auctionId, adId) { | ||
if ([auctionId, adId].every(isStr)) { | ||
return wurlMap[`${auctionId}${adId}`] | ||
} | ||
} | ||
|
||
function bidWonHandler(bid) { | ||
const wurl = getWurl(bid.auctionId, bid.adId) | ||
if (wurl) { | ||
logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) | ||
triggerPixel(wurl) | ||
removeWurl(bid.auctionId, bid.adId) | ||
} | ||
} | ||
|
||
registerBidder(spec) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Overview | ||
|
||
``` | ||
Module Name: Holid Bid Adapter | ||
Module Type: Bidder Adapter | ||
Maintainer: richard@holid.se | ||
``` | ||
|
||
# Description | ||
|
||
Currently module supports only banner mediaType. | ||
|
||
# Test Parameters | ||
|
||
## Sample Banner Ad Unit | ||
|
||
```js | ||
var adUnits = [ | ||
{ | ||
code: 'bannerAdUnit', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[300, 250]], | ||
}, | ||
}, | ||
bids: [ | ||
{ | ||
bidder: 'holid', | ||
params: { | ||
adUnitID: '12345', | ||
}, | ||
}, | ||
], | ||
}, | ||
] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import { expect } from 'chai' | ||
import { spec } from 'modules/holidBidAdapter.js' | ||
|
||
describe('holidBidAdapterTests', () => { | ||
const bidRequestData = { | ||
bidder: 'holid', | ||
adUnitCode: 'test-div', | ||
bidId: 'bid-id', | ||
auctionId: 'test-id', | ||
params: { adUnitID: '12345' }, | ||
mediaTypes: { banner: {} }, | ||
sizes: [[300, 250]], | ||
ortb2: { | ||
site: { | ||
publisher: { | ||
domain: 'https://foo.bar', | ||
} | ||
}, | ||
regs: { | ||
gdpr: 1, | ||
}, | ||
user: { | ||
ext: { | ||
consent: 'G4ll0p1ng_Un1c0rn5', | ||
} | ||
}, | ||
device: { | ||
h: 410, | ||
w: 1860, | ||
} | ||
} | ||
} | ||
|
||
describe('isBidRequestValid', () => { | ||
const bid = JSON.parse(JSON.stringify(bidRequestData)) | ||
|
||
it('should return true', () => { | ||
expect(spec.isBidRequestValid(bid)).to.equal(true) | ||
}) | ||
|
||
it('should return false when required params are not passed', () => { | ||
const bid = JSON.parse(JSON.stringify(bidRequestData)) | ||
delete bid.params.adUnitID | ||
|
||
expect(spec.isBidRequestValid(bid)).to.equal(false) | ||
}) | ||
}) | ||
|
||
describe('buildRequests', () => { | ||
const bid = JSON.parse(JSON.stringify(bidRequestData)) | ||
const request = spec.buildRequests([bid], bid) | ||
const payload = JSON.parse(request[0].data) | ||
|
||
it('should include ext in imp', () => { | ||
expect(payload.imp[0].ext).to.exist | ||
expect(payload.imp[0].ext).to.deep.equal({ | ||
prebid: { storedrequest: { id: '12345' } }, | ||
}) | ||
}) | ||
|
||
it('should include banner format in imp', () => { | ||
expect(payload.imp[0].banner).to.exist | ||
expect(payload.imp[0].banner).to.deep.equal({ | ||
format: [{ w: 300, h: 250 }], | ||
}) | ||
}) | ||
|
||
it('should include ortb2 first party data', () => { | ||
expect(payload.device.w).to.equal(1860) | ||
expect(payload.device.h).to.equal(410) | ||
expect(payload.user.ext.consent).to.equal('G4ll0p1ng_Un1c0rn5') | ||
expect(payload.regs.gdpr).to.equal(1) | ||
}) | ||
}) | ||
|
||
describe('interpretResponse', () => { | ||
const serverResponse = { | ||
body: { | ||
id: 'test-id', | ||
cur: 'USD', | ||
seatbid: [ | ||
{ | ||
bid: [ | ||
{ | ||
id: 'testbidid', | ||
price: 0.4, | ||
adm: 'test-ad', | ||
adid: 789456, | ||
crid: 1234, | ||
w: 300, | ||
h: 250, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
} | ||
|
||
const interpretedResponse = spec.interpretResponse( | ||
serverResponse, | ||
bidRequestData | ||
) | ||
|
||
it('should interpret response', () => { | ||
expect(interpretedResponse[0].requestId).to.equal(bidRequestData.bidId) | ||
expect(interpretedResponse[0].cpm).to.equal( | ||
serverResponse.body.seatbid[0].bid[0].price | ||
) | ||
expect(interpretedResponse[0].ad).to.equal( | ||
serverResponse.body.seatbid[0].bid[0].adm | ||
) | ||
expect(interpretedResponse[0].creativeId).to.equal( | ||
serverResponse.body.seatbid[0].bid[0].crid | ||
) | ||
expect(interpretedResponse[0].width).to.equal( | ||
serverResponse.body.seatbid[0].bid[0].w | ||
) | ||
expect(interpretedResponse[0].height).to.equal( | ||
serverResponse.body.seatbid[0].bid[0].h | ||
) | ||
expect(interpretedResponse[0].currency).to.equal(serverResponse.body.cur) | ||
}) | ||
}) | ||
|
||
describe('getUserSyncs', () => { | ||
it('should return user sync', () => { | ||
const optionsType = { | ||
iframeEnabled: true, | ||
pixelEnabled: true, | ||
} | ||
const serverResponse = [ | ||
{ | ||
body: { | ||
ext: { | ||
responsetimemillis: { | ||
'test seat 1': 2, | ||
'test seat 2': 1, | ||
}, | ||
}, | ||
}, | ||
}, | ||
] | ||
const gdprConsent = { | ||
gdprApplies: 1, | ||
consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', | ||
} | ||
const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' | ||
const expectedUserSyncs = [ | ||
{ | ||
type: 'iframe', | ||
url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig&usp_consent=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', | ||
}, | ||
] | ||
|
||
const userSyncs = spec.getUserSyncs( | ||
optionsType, | ||
serverResponse, | ||
gdprConsent, | ||
uspConsent | ||
) | ||
|
||
expect(userSyncs).to.deep.equal(expectedUserSyncs) | ||
}) | ||
}) | ||
}) |