Skip to content

Commit ebf3907

Browse files
m-figurski-allegroCopilotpatmmccanntkogut-allegro
authored
Allegro Bid Adapter: initial release (prebid#14111)
* Allegro Bid Adapter implementation * copilot review fixes * Update test/spec/modules/allegroBidAdapter_spec.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * tmp * Revert "tmp" This reverts commit a200026. * retry tests * trigger tests * send requests with `text/plain` header * update docs * update docs --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Patrick McCann <patmmccann@gmail.com> Co-authored-by: tkogut-allegro <tomasz.kogut@allegro.com>
1 parent 0452e8d commit ebf3907

3 files changed

Lines changed: 546 additions & 0 deletions

File tree

modules/allegroBidAdapter.js

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// jshint esversion: 6, es3: false, node: true
2+
'use strict';
3+
4+
import {registerBidder} from '../src/adapters/bidderFactory.js';
5+
import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js';
6+
import {ortbConverter} from '../libraries/ortbConverter/converter.js';
7+
import {config} from '../src/config.js';
8+
import {triggerPixel, logInfo, logError} from '../src/utils.js';
9+
10+
const BIDDER_CODE = 'allegro';
11+
const BIDDER_URL = 'https://prebid.rtb.allegrogroup.com/v1/rtb/prebid/bid';
12+
const GVLID = 1493;
13+
14+
/**
15+
* Traverses an OpenRTB bid request object and moves any ext objects into
16+
* DoubleClick (Google) style bracketed keys (e.g. ext -> [com.google.doubleclick.site]).
17+
* Also normalizes certain integer flags into booleans (e.g. gdpr: 1 -> true).
18+
* This mutates the provided request object in-place.
19+
*
20+
* @param request OpenRTB bid request being prepared for sending.
21+
*/
22+
function convertExtensionFields(request) {
23+
if (request.imp) {
24+
request.imp.forEach(imp => {
25+
if (imp.banner?.ext) {
26+
moveExt(imp.banner, '[com.google.doubleclick.banner_ext]')
27+
}
28+
if (imp.ext) {
29+
moveExt(imp, '[com.google.doubleclick.imp]')
30+
}
31+
});
32+
}
33+
34+
if (request.app?.ext) {
35+
moveExt(request.app, '[com.google.doubleclick.app]')
36+
}
37+
38+
if (request.site?.ext) {
39+
moveExt(request.site, '[com.google.doubleclick.site]')
40+
}
41+
42+
if (request.site?.publisher?.ext) {
43+
moveExt(request.site.publisher, '[com.google.doubleclick.publisher]')
44+
}
45+
46+
if (request.user?.ext) {
47+
moveExt(request.user, '[com.google.doubleclick.user]')
48+
}
49+
50+
if (request.user?.data) {
51+
request.user.data.forEach(data => {
52+
if (data.ext) {
53+
moveExt(data, '[com.google.doubleclick.data]')
54+
}
55+
});
56+
}
57+
58+
if (request.device?.ext) {
59+
moveExt(request.device, '[com.google.doubleclick.device]')
60+
}
61+
62+
if (request.device?.geo?.ext) {
63+
moveExt(request.device.geo, '[com.google.doubleclick.geo]')
64+
}
65+
66+
if (request.regs?.ext) {
67+
if (request.regs?.ext?.gdpr !== undefined) {
68+
request.regs.ext.gdpr = request.regs.ext.gdpr === 1;
69+
}
70+
71+
moveExt(request.regs, '[com.google.doubleclick.regs]')
72+
}
73+
74+
if (request.source?.ext) {
75+
moveExt(request.source, '[com.google.doubleclick.source]')
76+
}
77+
78+
if (request.ext) {
79+
moveExt(request, '[com.google.doubleclick.bid_request]')
80+
}
81+
}
82+
83+
/**
84+
* Moves an `ext` field from a given object to a new bracketed key, cloning its contents.
85+
* If object or ext is missing nothing is done.
86+
*
87+
* @param obj The object potentially containing `ext`.
88+
* @param {string} newKey The destination key name (e.g. '[com.google.doubleclick.site]').
89+
*/
90+
function moveExt(obj, newKey) {
91+
if (!obj || !obj.ext) {
92+
return;
93+
}
94+
const extCopy = {...obj.ext};
95+
delete obj.ext;
96+
obj[newKey] = extCopy;
97+
}
98+
99+
/**
100+
* Custom ORTB converter configuration adjusting request/imp level boolean coercions
101+
* and migrating extension fields depending on config. Provides `toORTB` and `fromORTB`
102+
* helpers used in buildRequests / interpretResponse.
103+
*/
104+
const converter = ortbConverter({
105+
context: {
106+
mediaType: BANNER,
107+
ttl: 360,
108+
netRevenue: true
109+
},
110+
111+
/**
112+
* Builds and post-processes a single impression object, coercing integer flags to booleans.
113+
*
114+
* @param {Function} buildImp Base builder provided by ortbConverter.
115+
* @param bidRequest Individual bid request from Prebid.
116+
* @param context Shared converter context.
117+
* @returns {Object} ORTB impression object.
118+
*/
119+
imp(buildImp, bidRequest, context) {
120+
const imp = buildImp(bidRequest, context);
121+
if (imp?.banner?.topframe !== undefined) {
122+
imp.banner.topframe = imp.banner.topframe === 1;
123+
}
124+
if (imp?.secure !== undefined) {
125+
imp.secure = imp.secure === 1;
126+
}
127+
return imp;
128+
},
129+
130+
/**
131+
* Builds the full ORTB request and normalizes integer flags. Optionally migrates ext fields
132+
* into Google style bracketed keys unless disabled via `allegro.convertExtensionFields` config.
133+
*
134+
* @param {Function} buildRequest Base builder provided by ortbConverter.
135+
* @param {Object[]} imps Array of impression objects.
136+
* @param bidderRequest Prebid bidderRequest (contains refererInfo, gdpr, etc.).
137+
* @param context Shared converter context.
138+
* @returns {Object} Mutated ORTB request object ready to serialize.
139+
*/
140+
request(buildRequest, imps, bidderRequest, context) {
141+
const request = buildRequest(imps, bidderRequest, context);
142+
143+
if (request?.device?.dnt !== undefined) {
144+
request.device.dnt = request.device.dnt === 1;
145+
}
146+
147+
if (request?.device?.sua?.mobile !== undefined) {
148+
request.device.sua.mobile = request.device.sua.mobile === 1;
149+
}
150+
151+
if (request?.test !== undefined) {
152+
request.test = request.test === 1;
153+
}
154+
155+
// by default, we convert extension fields unless the config explicitly disables it
156+
const convertExtConfig = config.getConfig('allegro.convertExtensionFields');
157+
if (convertExtConfig === undefined || convertExtConfig === true) {
158+
convertExtensionFields(request);
159+
}
160+
161+
if (request?.source?.schain && !isSchainValid(request.source.schain)) {
162+
delete request.source.schain;
163+
}
164+
165+
return request;
166+
}
167+
})
168+
169+
/**
170+
* Validates supply chain object structure
171+
* @param schain - Supply chain object
172+
* @return {boolean} True if valid, false otherwise
173+
*/
174+
function isSchainValid(schain) {
175+
try {
176+
if (!schain || !schain.nodes || !Array.isArray(schain.nodes)) {
177+
return false;
178+
}
179+
const requiredFields = ['asi', 'sid', 'hp'];
180+
return schain.nodes.every(node =>
181+
requiredFields.every(field => node.hasOwnProperty(field))
182+
);
183+
} catch (error) {
184+
logError('Allegro: Error validating schain:', error);
185+
return false;
186+
}
187+
}
188+
189+
/**
190+
* Allegro Bid Adapter specification object consumed by Prebid core.
191+
*/
192+
export const spec = {
193+
code: BIDDER_CODE,
194+
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
195+
gvlid: GVLID,
196+
197+
/**
198+
* Validates an incoming bid object.
199+
*
200+
* @param bid Prebid bid request params.
201+
* @returns {boolean} True if bid is considered valid.
202+
*/
203+
isBidRequestValid: function (bid) {
204+
return !!(bid);
205+
},
206+
207+
/**
208+
* Generates the network request payload for the adapter.
209+
*
210+
* @param bidRequests List of valid bid requests.
211+
* @param bidderRequest Aggregated bidder request data (gdpr, usp, refererInfo, etc.).
212+
* @returns Request details for Prebid to send.
213+
*/
214+
buildRequests: function (bidRequests, bidderRequest) {
215+
const url = config.getConfig('allegro.bidderUrl') || BIDDER_URL;
216+
217+
return {
218+
method: 'POST',
219+
url: url,
220+
data: converter.toORTB({bidderRequest, bidRequests}),
221+
options: {
222+
contentType: 'text/plain'
223+
},
224+
}
225+
},
226+
227+
/**
228+
* Parses the server response into Prebid bid objects.
229+
*
230+
* @param response Server response wrapper from Prebid XHR (expects `body`).
231+
* @param request Original request object passed to server (contains `data`).
232+
*/
233+
interpretResponse: function (response, request) {
234+
if (!response.body) return;
235+
return converter.fromORTB({response: response.body, request: request.data}).bids;
236+
},
237+
238+
/**
239+
* Fires impression tracking pixel when the bid wins if enabled by config.
240+
*
241+
* @param bid The winning bid object.
242+
*/
243+
onBidWon: function (bid) {
244+
const triggerImpressionPixel = config.getConfig('allegro.triggerImpressionPixel');
245+
246+
if (triggerImpressionPixel && bid.burl) {
247+
triggerPixel(bid.burl);
248+
}
249+
250+
if (config.getConfig('debug')) {
251+
logInfo('bid won', bid);
252+
}
253+
}
254+
255+
}
256+
257+
registerBidder(spec);

modules/allegroBidAdapter.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Overview
2+
3+
**Module Name**: Allegro Bidder Adapter
4+
**Module Type**: Bidder Adapter
5+
**Maintainer**: the-bidders@allegro.com
6+
**GVLID**: 1493
7+
8+
# Description
9+
10+
Connects to Allegro's demand sources for banner advertising. This adapter uses the OpenRTB 2.5 protocol with support for extension field conversion to Google DoubleClick proto format.
11+
12+
# Supported Media Types
13+
14+
- Banner
15+
- Native
16+
- Video
17+
18+
# Configuration
19+
20+
The Allegro adapter supports the following configuration options:
21+
22+
## Global Configuration Parameters
23+
24+
| Name | Scope | Type | Description | Default |
25+
|----------------------------------|----------|---------|-----------------------------------------------------------------------------|---------------------------------------------------------|
26+
| `allegro.bidderUrl` | optional | String | Custom bidder endpoint URL | `https://prebid.rtb.allegrogroup.com/v1/rtb/prebid/bid` |
27+
| `allegro.convertExtensionFields` | optional | Boolean | Enable/disable conversion of OpenRTB extension fields to DoubleClick format | `true` |
28+
| `allegro.triggerImpressionPixel` | optional | Boolean | Enable/disable triggering impression tracking pixels on bid won event | `false` |
29+
30+
## Configuration example
31+
32+
```javascript
33+
pbjs.setConfig({
34+
allegro: {
35+
triggerImpressionPixel: true
36+
}
37+
});
38+
```
39+
40+
# AdUnit Configuration Example
41+
42+
## Banner Ads
43+
44+
```javascript
45+
var adUnits = [{
46+
code: 'banner-ad-div',
47+
mediaTypes: {
48+
banner: {
49+
sizes: [
50+
[300, 250],
51+
[728, 90],
52+
[300, 600]
53+
]
54+
}
55+
},
56+
bids: [{
57+
bidder: 'allegro'
58+
}]
59+
}];
60+
```
61+
62+
# Features
63+
## Impression Tracking
64+
65+
When `allegro.triggerImpressionPixel` is enabled, the adapter will automatically fire the provided `burl` (billing/impression) tracking URL when a bid wins.
66+
67+
# Technical Details
68+
69+
- **Protocol**: OpenRTB 2.5
70+
- **TTL**: 360 seconds
71+
- **Net Revenue**: true
72+
- **Content Type**: text/plain
73+

0 commit comments

Comments
 (0)