Skip to content

Commit 05e4853

Browse files
kurrestahlbergjaiminpanchal27
authored andcommitted
Add ReadPeak Bid Adapter (#1838)
* Add ReadPeak Bid Adapter * Remove protocol from endpoint address * Make bidfloor optional * Update ReadPeak Bidder tests * Fix creative id, remove ad id from response. * Fix tests * Drop bidderCode * Fix tests
1 parent 9e89e1f commit 05e4853

File tree

3 files changed

+461
-0
lines changed

3 files changed

+461
-0
lines changed

modules/readpeakBidAdapter.js

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import {logError, getTopWindowLocation} from 'src/utils';
2+
import { registerBidder } from 'src/adapters/bidderFactory';
3+
4+
export const ENDPOINT = '//app.readpeak.com/header/prebid';
5+
6+
const NATIVE_DEFAULTS = {
7+
TITLE_LEN: 70,
8+
DESCR_LEN: 120,
9+
SPONSORED_BY_LEN: 50,
10+
IMG_MIN: 150,
11+
ICON_MIN: 50,
12+
CTA_LEN: 50,
13+
};
14+
15+
const BIDDER_CODE = 'readpeak'
16+
17+
export const spec = {
18+
19+
code: BIDDER_CODE,
20+
21+
supportedMediaTypes: ['native'],
22+
23+
isBidRequestValid: bid => (
24+
!!(bid && bid.params && bid.params.publisherId && bid.nativeParams)
25+
),
26+
27+
buildRequests: bidRequests => {
28+
const request = {
29+
id: bidRequests[0].bidderRequestId,
30+
imp: bidRequests.map(slot => impression(slot)).filter(imp => imp.native != null),
31+
site: site(bidRequests),
32+
app: app(bidRequests),
33+
device: device(),
34+
isPrebid: true,
35+
}
36+
37+
return {
38+
method: 'POST',
39+
url: ENDPOINT,
40+
data: JSON.stringify(request),
41+
}
42+
},
43+
44+
interpretResponse: (response, request) => (
45+
bidResponseAvailable(request, response)
46+
),
47+
};
48+
49+
function bidResponseAvailable(bidRequest, bidResponse) {
50+
const idToImpMap = {};
51+
const idToBidMap = {};
52+
if (!bidResponse['body']) {
53+
return []
54+
}
55+
bidResponse = bidResponse.body
56+
parse(bidRequest.data).imp.forEach(imp => {
57+
idToImpMap[imp.id] = imp;
58+
});
59+
if (bidResponse) {
60+
bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => {
61+
idToBidMap[bid.impid] = bid;
62+
}));
63+
}
64+
const bids = [];
65+
Object.keys(idToImpMap).forEach(id => {
66+
if (idToBidMap[id]) {
67+
const bid = {
68+
requestId: id,
69+
cpm: idToBidMap[id].price,
70+
creativeId: idToBidMap[id].crid,
71+
ttl: 300,
72+
netRevenue: true,
73+
mediaType: 'native',
74+
currency: bidResponse.cur,
75+
native: nativeResponse(idToImpMap[id], idToBidMap[id]),
76+
};
77+
bids.push(bid);
78+
}
79+
});
80+
return bids;
81+
}
82+
83+
function impression(slot) {
84+
return {
85+
id: slot.bidId,
86+
native: nativeImpression(slot),
87+
bidfloor: slot.params.bidfloor || 0,
88+
bidfloorcur: slot.params.bidfloorcur || 'USD'
89+
};
90+
}
91+
92+
function nativeImpression(slot) {
93+
if (slot.nativeParams) {
94+
const assets = [];
95+
addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN));
96+
addAsset(assets, imageAsset(2, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN));
97+
addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN));
98+
addAsset(assets, dataAsset(4, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN));
99+
addAsset(assets, dataAsset(5, slot.nativeParams.cta, 12, NATIVE_DEFAULTS.CTA_LEN));
100+
return {
101+
request: JSON.stringify({ assets }),
102+
ver: '1.1',
103+
};
104+
}
105+
return null;
106+
}
107+
108+
function addAsset(assets, asset) {
109+
if (asset) {
110+
assets.push(asset);
111+
}
112+
}
113+
114+
function titleAsset(id, params, defaultLen) {
115+
if (params) {
116+
return {
117+
id,
118+
required: params.required ? 1 : 0,
119+
title: {
120+
len: params.len || defaultLen,
121+
},
122+
};
123+
}
124+
return null;
125+
}
126+
127+
function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) {
128+
return params ? {
129+
id,
130+
required: params.required ? 1 : 0,
131+
img: {
132+
type,
133+
wmin: params.wmin || defaultMinWidth,
134+
hmin: params.hmin || defaultMinHeight,
135+
}
136+
} : null;
137+
}
138+
139+
function dataAsset(id, params, type, defaultLen) {
140+
return params ? {
141+
id,
142+
required: params.required ? 1 : 0,
143+
data: {
144+
type,
145+
len: params.len || defaultLen,
146+
}
147+
} : null;
148+
}
149+
150+
function site(bidderRequest) {
151+
const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.publisherId : '0';
152+
const appParams = bidderRequest[0].params.app;
153+
if (!appParams) {
154+
return {
155+
publisher: {
156+
id: pubId.toString(),
157+
},
158+
id: pubId.toString(),
159+
ref: referrer(),
160+
page: getTopWindowLocation().href,
161+
domain: getTopWindowLocation().hostname
162+
}
163+
}
164+
return null;
165+
}
166+
167+
function app(bidderRequest) {
168+
const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.publisherId : '0';
169+
const appParams = bidderRequest[0].params.app;
170+
if (appParams) {
171+
return {
172+
publisher: {
173+
id: pubId.toString(),
174+
},
175+
bundle: appParams.bundle,
176+
storeurl: appParams.storeUrl,
177+
domain: appParams.domain,
178+
}
179+
}
180+
return null;
181+
}
182+
183+
function referrer() {
184+
try {
185+
return window.top.document.referrer;
186+
} catch (e) {
187+
return document.referrer;
188+
}
189+
}
190+
191+
function device() {
192+
return {
193+
ua: navigator.userAgent,
194+
language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage),
195+
};
196+
}
197+
198+
function parse(rawResponse) {
199+
try {
200+
if (rawResponse) {
201+
if (typeof rawResponse === 'object') {
202+
return rawResponse
203+
} else {
204+
return JSON.parse(rawResponse);
205+
}
206+
}
207+
} catch (ex) {
208+
logError('readpeakBidAdapter.safeParse', 'ERROR', ex);
209+
}
210+
return null;
211+
}
212+
213+
function nativeResponse(imp, bid) {
214+
if (imp && imp['native']) {
215+
const nativeAd = parse(bid.adm);
216+
const keys = {};
217+
if (nativeAd && nativeAd.assets) {
218+
nativeAd.assets.forEach(asset => {
219+
keys.title = asset.title ? asset.title.text : keys.title;
220+
keys.body = asset.data && asset.id === 4 ? asset.data.value : keys.body;
221+
keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy;
222+
keys.image = asset.img && asset.id === 2 ? asset.img.url : keys.image;
223+
keys.cta = asset.data && asset.id === 5 ? asset.data.value : keys.cta;
224+
});
225+
if (nativeAd.link) {
226+
keys.clickUrl = encodeURIComponent(nativeAd.link.url);
227+
}
228+
keys.impressionTrackers = nativeAd.imptrackers;
229+
return keys;
230+
}
231+
}
232+
return null;
233+
}
234+
235+
registerBidder(spec);

modules/readpeakBidAdapter.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Overview
2+
3+
Module Name: ReadPeak Bid Adapter
4+
5+
Module Type: Bidder Adapter
6+
7+
Maintainer: [email protected]
8+
9+
# Description
10+
11+
Module that connects to ReadPeak's demand sources
12+
13+
This adapter requires setup and approval from ReadPeak.
14+
Please reach out to your account team or [email protected] for more information.
15+
16+
# Test Parameters
17+
```javascript
18+
var adUnits = [{
19+
code: 'test-native',
20+
mediaTypes: { native: { type: 'image' } },
21+
bids: [{
22+
bidder: 'readpeak',
23+
params: {
24+
bidfloor: 5.00,
25+
publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76'
26+
},
27+
}]
28+
}];
29+
```

0 commit comments

Comments
 (0)