Proposal: pbjs-ortb conversion library
Type of issue
Feature
Description
There is an increasing number of adapters that interface with ORTB backends. Currently, each needs to replicate logic to convert PBJS bid objects to ORTB and back again; a dialect of that logic is also implemented in the PBS adapter.
We should extract that into one canonical implementation, and make it trivial to add a new adapter if it talks to an ORTB backend.
Proposal
-
Move the enrichmentFpdModule logic into core and extend it to populate any ortb2 field that is not specific to the impression or bidder (for example,
device.ua). Currently this is left to each individual adapter, but they rarely should have a reason to differ in their implementation. This is true for "traditional" (non-ORTB) adapters as well; consolidating these items into first-party data should allow some simplification in that regard too. -
Implement utilities
bids2ortbandortb2bidssuch that an adapter may use them in this manner:
buildRequests(bidRequests, bidderRequest) {
return {
method: ...,
url: ...,
data: bids2ortb({bidderRequest})
}
},
interpretResponse(response, request) {
return ortb2bids({response: response.body, request: request.data})
}
these utilities should handle everything that the PBS adapter now understands, namely:
- banner, video, and native mediatypes
- coppa / gdpr / usp
- price floors
- multibid
- schain
- gpid / pbadslot
- any others?
Adapter-specific customization
Current ortb bidders are surprisingly similar, but not identical. Adapters are free to modify the return value they get from bids2ortb / ortb2bids, but that pattern is not ideal for some cases.
Bid filtering
The Rubicon adapter wants to create a separate ortb request for each one in a set of particular bids: https://github.com/prebid/Prebid.js/blob/48ad7190decf77cdda00827b38f334856eff4de8/modules/rubiconBidAdapter.js#L179-L183
Proposed solution - accept an optional list of bid requests in bids2ortb:
const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video')
.map(bidRequest => bids2ortb({bidderRequest, bidRequests: [bidRequest]})
Bid-level parameters
For example, from the ADF adapter:
https://github.com/prebid/Prebid.js/blob/f7dca5b3d5be74855f3d466776a02ff13b74831e/modules/adfBidAdapter.js#L107-L120
Proposed solution - provide an imp-level override in bids2ortb:
bids2ortb({
bidderRequest,
imp: function(bid2imp, bidRequest) {
const { mid, inv, mname } = bidRequest.params;
return mergeDeep(bid2Imp({bidRequest}), {
tagid: mid,
ext: {
bidder: {
inv,
mname
}
}
});
}
});
The same idea can be applied on the response side - for example ADF's video renderer:
https://github.com/prebid/Prebid.js/blob/48ad7190decf77cdda00827b38f334856eff4de8/modules/adfBidAdapter.js#L272-L275
could look like:
ortb2bids({
request,
response,
bid: function(imp2bid, imp, bidRequest) {
const bid = imp2bid({imp});
if (bidRequest.mediaType === VIDEO && deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') {
bid.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode});
bid.renderer.setRender(renderer);
}
return bid;
}
})
Feature override
Because it makes practical sense to separate the ortb implementation of individual features (e.g. bidfloor should only be populated if the priceFloors module is installed), it would be relatively straightforward to allow adapters to override their translation. I have not found a great use case for this yet but I'll use the OpenXNew adapter as inspiration:
https://github.com/prebid/Prebid.js/blob/df48483e0e122a635884c2ca763db48487fbf185/modules/openxOrtbBidAdapter.js#L90-L95
Suppose that logic was changed to say "if bid.params.customFloor is defined, use that instead of the 'real' floor". We could allow that with something like:
bids2ortb({
bidderRequest,
impOverride: {
bidfloor: function(applyOriginal, imp, bidRequest) {
if (bidRequest.params.customFloor) {
Object.assign(imp, {
bidfloor: bidRequest.params.customFloor,
bidfloorcur: 'USD'
})
} else {
applyOriginal(imp, bidRequest);
}
}
}
})
which would mean "override the ortb translation logic for the bidfloor feature with this custom logic instead". The difference with simply overwriting the contents of imp like we did in the previous example is that:
- the original logic would not run at all - which could conceivably help performance if the feature is complex enough;
- related fields are grouped together under a single feature name, which allows adapters to treat them as a logical entity without worrying about changes in core. For example an adapter that is not interested in GPDR could disable it with:
instead of learning what fields are currently set by core for gdpr and listing them like so:return bids2ortb({ bidderRequest override: { gdpr: function(){} } })const request = bids2ortb({bidderRequest}); delete request.regs?.ext?.gdpr; delete request.user?.ext?.consent; delete request.user?.ext?.ConsentedProvidersSettings?.consented_providers; return request;