Skip to content

Commit 8f73bb9

Browse files
aszydlo-rasclaude
andcommitted
DAS Bid Adapter: rename from ringieraxelspringerBidAdapter
- Rename ringieraxelspringerBidAdapter to dasBidAdapter - Update module code from 'ringieraxelspringer' to 'das' - Add raspUtils library dependency for native response parsing - Update documentation and test files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent df5d17b commit 8f73bb9

File tree

6 files changed

+860
-1086
lines changed

6 files changed

+860
-1086
lines changed

libraries/raspUtils/raspUtils.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { deepAccess } from '../../src/utils.js';
2+
3+
const GDE_SCRIPT_URL = 'https://ocdn.eu/adp/static/embedgde/latest/bundle.min.js';
4+
const GDE_PARAM_PREFIX = 'gde_';
5+
const REQUIRED_GDE_PARAMS = [
6+
'gde_subdomena',
7+
'gde_id',
8+
'gde_stparam',
9+
'gde_fastid',
10+
'gde_inscreen'
11+
];
12+
13+
export function parseNativeResponse(ad) {
14+
if (!(ad.data?.fields && ad.data?.meta)) {
15+
return false;
16+
}
17+
18+
const { click, Thirdpartyimpressiontracker, Thirdpartyimpressiontracker2, thirdPartyClickTracker2, imp, impression, impression1, impressionJs1, image, Image, title, leadtext, url, Calltoaction, Body, Headline, Thirdpartyclicktracker, adInfo, partner_logo: partnerLogo } = ad.data.fields;
19+
20+
const { dsaurl, height, width, adclick } = ad.data.meta;
21+
const emsLink = ad.ems_link;
22+
const link = adclick + (url || click);
23+
const nativeResponse = {
24+
sendTargetingKeys: false,
25+
title: title || Headline || '',
26+
image: {
27+
url: image || Image || '',
28+
width,
29+
height
30+
},
31+
icon: {
32+
url: partnerLogo || '',
33+
width,
34+
height
35+
},
36+
clickUrl: link,
37+
cta: Calltoaction || '',
38+
body: leadtext || Body || '',
39+
body2: adInfo || '',
40+
sponsoredBy: deepAccess(ad, 'data.meta.advertiser_name', null) || '',
41+
};
42+
43+
nativeResponse.impressionTrackers = [emsLink, imp, impression, impression1, Thirdpartyimpressiontracker, Thirdpartyimpressiontracker2].filter(Boolean);
44+
nativeResponse.javascriptTrackers = [impressionJs1, getGdeScriptUrl(ad.data.fields)].map(url => url ? `<script async src=${url}></script>` : null).filter(Boolean);
45+
nativeResponse.clickTrackers = [Thirdpartyclicktracker, thirdPartyClickTracker2].filter(Boolean);
46+
47+
if (dsaurl) {
48+
nativeResponse.privacyLink = dsaurl;
49+
}
50+
51+
return nativeResponse
52+
}
53+
54+
const getGdeScriptUrl = (adDataFields) => {
55+
if (REQUIRED_GDE_PARAMS.every(param => adDataFields[param])) {
56+
const params = new URLSearchParams();
57+
Object.entries(adDataFields)
58+
.filter(([key]) => key.startsWith(GDE_PARAM_PREFIX))
59+
.forEach(([key, value]) => params.append(key, value));
60+
61+
return `${GDE_SCRIPT_URL}?${params.toString()}`;
62+
}
63+
return null;
64+
}

modules/dasBidAdapter.js

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js';
2+
import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js';
3+
import { parseNativeResponse } from '../libraries/raspUtils/raspUtils.js';
4+
import { registerBidder } from '../src/adapters/bidderFactory.js';
5+
import { BANNER, NATIVE } from '../src/mediaTypes.js';
6+
import { deepAccess, safeJSONParse } from '../src/utils.js';
7+
8+
const BIDDER_CODE = 'das';
9+
10+
const getEndpoint = (network) => {
11+
return `https://csr.onet.pl/${encodeURIComponent(network)}/bid`;
12+
};
13+
14+
function parseParams(params, bidderRequest) {
15+
const customParams = {};
16+
const keyValues = {};
17+
18+
if (params.adbeta) {
19+
customParams.adbeta = params.adbeta;
20+
}
21+
22+
if (params.site) {
23+
customParams.site = params.site;
24+
}
25+
26+
if (params.area) {
27+
customParams.area = params.area;
28+
}
29+
30+
if (params.network) {
31+
customParams.network = params.network;
32+
}
33+
34+
// Custom parameters
35+
if (params.customParams && typeof params.customParams === 'object') {
36+
Object.assign(customParams, params.customParams);
37+
}
38+
39+
const pageContext = params.pageContext;
40+
if (pageContext) {
41+
// Document URL override
42+
if (pageContext.du) {
43+
customParams.du = pageContext.du;
44+
}
45+
46+
// Referrer override
47+
if (pageContext.dr) {
48+
customParams.dr = pageContext.dr;
49+
}
50+
51+
// Document virtual address
52+
if (pageContext.dv) {
53+
customParams.DV = pageContext.dv;
54+
}
55+
56+
// Keywords
57+
const keywords = getAllOrtbKeywords(
58+
bidderRequest?.ortb2,
59+
pageContext.keyWords,
60+
);
61+
if (keywords.length > 0) {
62+
customParams.kwrd = keywords.join('+');
63+
}
64+
65+
// Local capping
66+
if (pageContext.capping) {
67+
customParams.local_capping = pageContext.capping;
68+
}
69+
70+
// Key values
71+
if (pageContext.keyValues && typeof pageContext.keyValues === 'object') {
72+
Object.entries(pageContext.keyValues).forEach(([key, value]) => {
73+
keyValues[`kv${key}`] = value;
74+
});
75+
}
76+
}
77+
78+
const du = customParams.du || deepAccess(bidderRequest, 'refererInfo.page');
79+
const dr = customParams.dr || deepAccess(bidderRequest, 'refererInfo.ref');
80+
81+
if (du) customParams.du = du;
82+
if (dr) customParams.dr = dr;
83+
84+
const dsaRequired = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa.required');
85+
if (dsaRequired !== undefined) {
86+
customParams.dsainfo = dsaRequired;
87+
}
88+
89+
return {
90+
customParams,
91+
keyValues,
92+
};
93+
}
94+
95+
function buildUserIds(customParams) {
96+
const userIds = {};
97+
if (customParams.lu) {
98+
userIds.lu = customParams.lu;
99+
}
100+
if (customParams.aid) {
101+
userIds.aid = customParams.aid;
102+
}
103+
return userIds;
104+
}
105+
106+
function getNpaFromPubConsent(pubConsent) {
107+
const params = new URLSearchParams(pubConsent);
108+
return params.get('npa') === '1';
109+
}
110+
111+
function buildOpenRTBRequest(bidRequests, bidderRequest) {
112+
const { customParams, keyValues } = parseParams(
113+
bidRequests[0].params,
114+
bidderRequest,
115+
);
116+
const imp = bidRequests.map((bid) => {
117+
const sizes = getAdUnitSizes(bid);
118+
const imp = {
119+
id: bid.bidId,
120+
tagid: bid.params.slot,
121+
secure: 1,
122+
};
123+
if (bid.params.slotSequence) {
124+
imp.ext = {
125+
pos: String(bid.params.slotSequence)
126+
}
127+
}
128+
129+
if (bid.mediaTypes?.banner) {
130+
imp.banner = {
131+
format: sizes.map((size) => ({
132+
w: size[0],
133+
h: size[1],
134+
})),
135+
};
136+
}
137+
if (bid.mediaTypes?.native) {
138+
imp.native = {
139+
request: '{}',
140+
ver: '1.2',
141+
};
142+
}
143+
144+
return imp;
145+
});
146+
147+
const request = {
148+
id: bidderRequest.bidderRequestId,
149+
imp,
150+
site: {
151+
...bidderRequest.ortb2?.site,
152+
id: customParams.site,
153+
page: customParams.du,
154+
ref: customParams.dr,
155+
ext: {
156+
...bidderRequest.ortb2?.site?.ext,
157+
area: customParams.area,
158+
kwrd: customParams.kwrd,
159+
dv: customParams.DV
160+
},
161+
},
162+
user: {
163+
ext: {
164+
ids: buildUserIds(customParams),
165+
},
166+
},
167+
ext: {
168+
network: customParams.network,
169+
keyvalues: keyValues,
170+
},
171+
at: 1,
172+
tmax: bidderRequest.timeout
173+
};
174+
175+
if (customParams.adbeta) {
176+
request.ext.adbeta = customParams.adbeta;
177+
}
178+
179+
if (bidderRequest.device) {
180+
request.device = bidderRequest.device;
181+
}
182+
183+
if (bidderRequest.gdprConsent) {
184+
request.user = {
185+
ext: {
186+
npa: getNpaFromPubConsent(customParams.pubconsent),
187+
localcapping: customParams.local_capping,
188+
localadpproduts: customParams.adp_products,
189+
...request.user.ext,
190+
},
191+
};
192+
request.regs = {
193+
gpp: bidderRequest.gdprConsent.consentString,
194+
gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0,
195+
ext: {
196+
dsa: customParams.dsainfo,
197+
},
198+
}
199+
}
200+
201+
return request;
202+
}
203+
204+
function prepareNativeMarkup(bid) {
205+
const parsedNativeMarkup = safeJSONParse(bid.adm)
206+
const ad = {
207+
data: parsedNativeMarkup || {},
208+
ems_link: bid.ext?.ems_link || '',
209+
};
210+
const nativeResponse = parseNativeResponse(ad) || {};
211+
return nativeResponse;
212+
}
213+
214+
function interpretResponse(serverResponse) {
215+
const bidResponses = [];
216+
const response = serverResponse.body;
217+
218+
if (!response || !response.seatbid || !response.seatbid.length) {
219+
return bidResponses;
220+
}
221+
222+
response.seatbid.forEach((seatbid) => {
223+
seatbid.bid.forEach((bid) => {
224+
const bidResponse = {
225+
requestId: bid.impid,
226+
cpm: bid.price,
227+
currency: response.cur || 'USD',
228+
width: bid.w,
229+
height: bid.h,
230+
creativeId: bid.crid || bid.id,
231+
netRevenue: true,
232+
dealId: bid.dealid || null,
233+
actgMatch: bid.ext?.actgMatch || 0,
234+
ttl: 300,
235+
meta: {
236+
advertiserDomains: bid.adomain || [],
237+
},
238+
};
239+
240+
if (bid.mtype === 1) {
241+
bidResponse.mediaType = BANNER;
242+
bidResponse.ad = bid.adm;
243+
} else if (bid.mtype === 4) {
244+
bidResponse.mediaType = NATIVE;
245+
bidResponse.native = prepareNativeMarkup(bid);
246+
delete bidResponse.ad;
247+
}
248+
bidResponses.push(bidResponse);
249+
});
250+
});
251+
252+
return bidResponses;
253+
}
254+
255+
export const spec = {
256+
code: BIDDER_CODE,
257+
supportedMediaTypes: [BANNER, NATIVE],
258+
259+
isBidRequestValid: function (bid) {
260+
if (!bid || !bid.params) {
261+
return false;
262+
}
263+
return !!(
264+
bid.params?.network &&
265+
bid.params?.site &&
266+
bid.params?.area &&
267+
bid.params?.slot
268+
);
269+
},
270+
271+
buildRequests: function (validBidRequests, bidderRequest) {
272+
const data = buildOpenRTBRequest(validBidRequests, bidderRequest);
273+
const jsonData = JSON.stringify(data);
274+
const baseUrl = getEndpoint(data.ext.network);
275+
const fullUrl = `${baseUrl}?data=${encodeURIComponent(jsonData)}`;
276+
277+
// Switch to POST if URL exceeds 8k characters
278+
if (fullUrl.length > 8192) {
279+
return {
280+
method: 'POST',
281+
url: baseUrl,
282+
data: jsonData,
283+
options: {
284+
withCredentials: true,
285+
crossOrigin: true,
286+
customHeaders: {
287+
'Content-Type': 'text/plain'
288+
}
289+
},
290+
};
291+
}
292+
293+
return {
294+
method: 'GET',
295+
url: fullUrl,
296+
options: {
297+
withCredentials: true,
298+
crossOrigin: true,
299+
},
300+
};
301+
},
302+
303+
interpretResponse: function (serverResponse) {
304+
return interpretResponse(serverResponse);
305+
},
306+
};
307+
308+
registerBidder(spec);

0 commit comments

Comments
 (0)