Skip to content

Commit

Permalink
Yieldlove Bid Adapter: Initial Release (prebid#10175)
Browse files Browse the repository at this point in the history
* YieldloveBidAdapter: Release

* Update modules/yieldloveBidAdapter.md

* Update modules/yieldloveBidAdapter.js

* Update yieldloveBidAdapter.md

* Update yieldloveBidAdapter_spec.js

* Update yieldloveBidAdapter.md

* always send bid request per https

* fix auctionId leak

---------

Co-authored-by: PascalSalesch <pascal.salesch@yieldlove.com>
  • Loading branch information
PascalSalesch and PascalSalesch authored Sep 11, 2023
1 parent 78269b6 commit a81323f
Show file tree
Hide file tree
Showing 3 changed files with 315 additions and 0 deletions.
149 changes: 149 additions & 0 deletions modules/yieldloveBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import * as utils from '../src/utils.js';
import {BANNER} from '../src/mediaTypes.js';

const ENDPOINT_URL = 'https://s2s.yieldlove-ad-serving.net/openrtb2/auction';

const DEFAULT_BID_TTL = 300; /* 5 minutes */
const DEFAULT_CURRENCY = 'EUR';

const participatedBidders = []

export const spec = {
gvlid: 251,
code: 'yieldlove',
aliases: [],
supportedMediaTypes: [BANNER],

isBidRequestValid: function (bid) {
return !!(bid.params.pid && bid.params.rid)
},

buildRequests: function (validBidRequests, bidderRequest) {
const anyValidBidRequest = validBidRequests[0]

const impressions = validBidRequests.map(bidRequest => {
return {
ext: {
prebid: {
storedrequest: {
id: bidRequest.params.pid?.toString()
}
}
},
banner: {
format: bidRequest.sizes.map(sizeArr => ({
w: sizeArr[0],
h: sizeArr[1],
}))
},
secure: 1,
id: bidRequest.bidId
}
})

const s2sRequest = {
device: {
ua: window.navigator.userAgent,
w: window.innerWidth,
h: window.innerHeight,
},
site: {
ver: '1.9.0',
publisher: {
id: anyValidBidRequest.params.rid
},
page: window.location.href,
domain: anyValidBidRequest.params.rid
},
ext: {
prebid: {
targeting: {},
cache: {
bids: {}
},
storedrequest: {
id: anyValidBidRequest.params.rid
},
}
},
user: {
ext: {
consent: bidderRequest.gdprConsent?.consentString
},
},
id: utils.generateUUID(),
imp: impressions,
regs: {
ext: {
gdpr: 1
}
}
}

return {
method: 'POST',
url: ENDPOINT_URL,
data: s2sRequest,
options: {
contentType: 'text/plain',
withCredentials: true
},
};
},

interpretResponse: function (serverResponse) {
const bidResponses = []
const seatBids = serverResponse.body?.seatbid || []
seatBids.reduce((bids, cur) => {
if (cur.bid && cur.bid.length > 0) bids = bids.concat(cur.bid)
return bids
}, []).forEach(bid => {
bidResponses.push({
requestId: bid.impid,
cpm: bid.price,
width: bid.w,
height: bid.h,
ad: bid.adm,
ttl: DEFAULT_BID_TTL,
creativeId: bid.crid,
netRevenue: true,
currency: DEFAULT_CURRENCY
})
})

const bidders = serverResponse.body?.ext.responsetimemillis || {}
Object.keys(bidders).forEach(bidder => {
if (!participatedBidders.includes(bidder)) participatedBidders.push(bidder)
})

if (bidResponses.length === 0) {
utils.logInfo('interpretResponse :: no bid');
}

return bidResponses;
},

getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
const syncs = []

let gdprParams = ''
gdprParams = `gdpr=${Number(gdprConsent?.gdprApplies)}&`
gdprParams += `gdpr_consent=${gdprConsent?.consentString || ''}`

let bidderParams = ''
if (participatedBidders.length > 0) {
bidderParams = `bidders=${participatedBidders.join(',')}`
}

syncs.push({
type: 'iframe',
url: `https://cdn-a.yieldlove.com/load-cookie.html?endpoint=yieldlove&max_sync_count=100&${gdprParams}&${bidderParams}`
})

return syncs
},

};

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

```
Module Name: Yieldlove Bid Adapter
Module Type: Bidder Adapter
Maintainer: adapter@yieldlove.com
```


# Description

Connects to **[Yieldlove](https://www.yieldlove.com/)**s S2S platform for bids.

```js
const adUnits = [
{
code: 'test-div',
mediaTypes: { banner: { sizes: [[300, 250]] }},
bids: [
{
bidder: 'yieldlove',
params: {
pid: 34437,
rid: 'website.com'
}
}
]
}
]
```


# Bid Parameters

| Name | Scope | Description | Example | Type |
|---------------|--------------|---------------------------------------------------------|----------------------------|--------------|
| rid | **required** | Publisher ID on the Yieldlove platform | `website.com` | String |
| pid | **required** | Placement ID on the Yieldlove platform | `34437` | Number |
128 changes: 128 additions & 0 deletions test/spec/modules/yieldloveBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { expect } from 'chai';
import { spec } from 'modules/yieldloveBidAdapter.js';

const ENDPOINT_URL = 'https://s2s.yieldlove-ad-serving.net/openrtb2/auction';

// test params
const pid = 34437;
const rid = 'website.com';

describe('Yieldlove Bid Adaper', function () {
const bidRequests = [
{
'bidder': 'yieldlove',
'adUnitCode': 'adunit-code',
'sizes': [ [300, 250] ],
'params': {
pid,
rid
}
}
];

const serverResponse = {
body: {
seatbid: [
{
bid: [
{
impid: 'aaaa',
price: 0.5,
w: 300,
h: 250,
adm: '<div>test</div>',
crid: '1234',
}
]
}
],
ext: {}
}
}

describe('isBidRequestValid', () => {
const bid = bidRequests[0];

it('should return true when required params found', function () {
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false when required params are not present', function () {
const invalidBid = { ...bid, params: {} };
expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
});

it('should return false when required param "pid" is not present', function () {
const invalidBid = { ...bid, params: { ...bid.params, pid: undefined } };
expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
});

it('should return false when required param "rid" is not present', function () {
const invalidBid = { ...bid, params: { ...bid.params, rid: undefined } };
expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
});
});

describe('buildRequests', () => {
it('should build the request', function () {
const request = spec.buildRequests(bidRequests, {});
const payload = request.data;
const url = request.url;

expect(url).to.equal(ENDPOINT_URL);

expect(payload.site).to.exist;
expect(payload.site.publisher).to.exist;
expect(payload.site.publisher.id).to.exist;
expect(payload.site.publisher.id).to.equal(rid);
expect(payload.site.domain).to.exist;
expect(payload.site.domain).to.equal(rid);

expect(payload.imp).to.exist;
expect(payload.imp[0]).to.exist;
expect(payload.imp[0].ext).to.exist;
expect(payload.imp[0].ext.prebid).to.exist;
expect(payload.imp[0].ext.prebid.storedrequest).to.exist;
expect(payload.imp[0].ext.prebid.storedrequest.id).to.exist;
expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal(pid.toString());
});
});

describe('interpretResponse', () => {
it('should interpret the response by pushing it in the bids elem', function () {
const allResponses = spec.interpretResponse(serverResponse);
const response = allResponses[0];
const seatbid = serverResponse.body.seatbid[0].bid[0];

expect(response.requestId).to.exist;
expect(response.requestId).to.equal(seatbid.impid);
expect(response.cpm).to.exist;
expect(response.cpm).to.equal(seatbid.price);
expect(response.width).to.exist;
expect(response.width).to.equal(seatbid.w);
expect(response.height).to.exist;
expect(response.height).to.equal(seatbid.h);
expect(response.ad).to.exist;
expect(response.ad).to.equal(seatbid.adm);
expect(response.ttl).to.exist;
expect(response.creativeId).to.exist;
expect(response.creativeId).to.equal(seatbid.crid);
expect(response.netRevenue).to.exist;
expect(response.currency).to.exist;
});
});

describe('getUserSyncs', function() {
it('should retrieve user iframe syncs', function () {
expect(spec.getUserSyncs({ iframeEnabled: true }, [serverResponse], undefined, undefined)).to.deep.equal([{
type: 'iframe',
url: 'https://cdn-a.yieldlove.com/load-cookie.html?endpoint=yieldlove&max_sync_count=100&gdpr=NaN&gdpr_consent=&'
}]);

expect(spec.getUserSyncs({ iframeEnabled: true }, [serverResponse], { gdprApplies: true, consentString: 'example' }, undefined)).to.deep.equal([{
type: 'iframe',
url: 'https://cdn-a.yieldlove.com/load-cookie.html?endpoint=yieldlove&max_sync_count=100&gdpr=1&gdpr_consent=example&'
}]);
});
});
})

0 comments on commit a81323f

Please sign in to comment.