PBjs Core - OrtbConverter : dynamic renderer support
Type of change
-
[ ] Bugfix
-
[x] Feature
-
[ ] New bidder adapter
-
[ ] Code style update (formatting, local variables)
-
[ ] Refactoring (no functional changes, no api changes)
-
[ ] Build related changes
-
[ ] CI related changes
-
[ ] Does this change affect user-facing APIs or examples documented on http://prebid.org?
-
[ ] Other
Description of change
This is a draft PR that is related to github issue: https://github.com/prebid/Prebid.js/issues/10434
This is an implementation of adding dynamic renderer support into the Prebid Server bid adapter. This will allow a renderer to be specified into the bid response.
In the PR this rendererurl is specified via bid.seatbid[].bid[].ext.rendererurl. This is just used as an example, and can be changed.
Since the rendererurl is passed in via the bid response, the validation of the bid was changed to look for a rendererurl on the bid response.
@ccorbo we're unblocked now!
@patmmccann updated the PR this is ready for review
LGTM! @lksharma or @dgirardi please take a second look
@dgirardi The following test is failing in the build step. This PR shouldnt have any affect on this test. I am seeing this test fail locally on master branch as well. Could you kick off a retry in the build step, its only failing on safari it looks like.
1) should initialize LiveConnect and forward the prebid version when decode and emit an event
LiveIntentId
TypeError: undefined is not an object (evaluating '_xhr.server.requests[0].url') (/tmp/_karma_webpack_8411/commons.js:340612)
```
Does this approach still allow the publisher to override the bidder-supplied renderer if they wish?
It works the same way as a client adapter's renderer.
Apparently that's one of three ways bids can have a renderer, in order of decreasing precedence:
adUnit.mediaTypes.(video|banner|native).rendererbid.rendereradUnit.renderer
(1) and (3) are controlled by the pub, so only (1) can override this.
If someone is using (3) (which we do not recommend), this could arguably be breaking.
Edit: it's more complicated than the above, but the bottom line is that it follows the same rules as client adapters.
We ought to define what Server-Side adapters should know about this rendererUrl. My suggestion would be to keep it simple-from-a-Prebid-perspective like:
"The URL must point to javascript code and be completely self-sufficient - i.e. there's no way for client-side configuration."
there's no way for client-side configuration
With just a single URL, I think this requires the renderer to "register" itself with Prebid in a standardized way. It would need to do something like
window._pbjsRenderers = window._pbjsRenderers || {}
window._pbjsRenderers[<rendererUrl>] = function(bid) {
....
}
so that the client can invoke the renderer. I think this needs to use a new name in the global scope since the pbjs global is not necessarily accessible from the renderer.
Alternatively, the response could include instructions on how to run the renderer (with something like rendererGlobal alongside rendererUrl).
Just noticed that the path is off here -- instead of seatbid[].bid[].ext.rendererurl, I think it's seatbid[].bid[].ext.prebid.meta.rendererurl
So a related topic came up in regards to mobile outstream. I'm swimming outside my lane here, but in the interest of trying to move both efforts forward, perhaps we can set up conventions like this:
- The URL returned in seatbid[].bid[].ext.prebid.meta.rendererurl must point to a javascript file.
- That javascript file must contain a function called "render()" that takes an adId.
- The rendering code in Prebid.js/PUC renderAd() recognizes this "self-contained render function scenario by the existence of just the URL and creates an iframe into which it places javascript that invokes the required render() function:
<iframe>
<script type="javascript" src="RENDERER_URL">
render(ADID);
</script>
</iframe>
- The inline render function then calls postMessage with the adId to get the ad markup. Note that we really want the same code to be able to work in both the PBJS and PBSDK context.
- Prebid-core responds to the postMessage, perhaps with the same logic currently done for PUC?
- The inline render function does what it needs to display.
Note: this can be for outstream, richmedia, pseudo-native, whatever. We shouldn't make assumptions about what's being rendered.
Question: would it be possible to just pass the render() function the whole creative body so it doesn't have to do the postMessage?
Errors:
- if the URL doesn't exist or its not javascript or there's no render() function, then render will fail. Unclear Prebid can detect this, but if so, it should throw an event.
Question: would it be possible to just pass the render() function the whole creative body so it doesn't have to do the postMessage?
Yes (and I assume SDK can do the same), by inserting a bit more logic around the call to render. However if we are defining a standard API and inviting everyone to rewrite their renderers we should consider:
- what do we mean by creative body? in JS the "natural" object to render is the bid response. What I'd call "body" varies depending on the media type and even if we limit it to say video and a vast URL I'm not sure that's enough info to render.
- what should we do about https://github.com/prebid/Prebid.js/issues/10780 - should the renderer have access to the top window? can we run it inside the creative iframe? (I am also very unsure of how outstream works in practice).
what do we mean by creative body?
The ORTB 'adm' field is the obvious choice. If it was VAST that was cached, perhaps the URL? But it would be nice to push people to deal with VAST directly or createObjectURL() rather than this silly client-side caching just to get an address.
should the renderer have access to the top window?
I think we should start with "no" until someone makes an ironclad case for it. Renderers should be able to display as long as they can get the creative body.
The ORTB 'adm' field is the obvious choice.
I'm not sure that's enough - how about width / height? burl / nurl ? Not to mention that we don't always have an ORTB response (not sure if that's also the case for the SDK).
width / height? burl / nurl ?
Ok then, pass the whole bidresponse object and let them figure it out.
The general idea is that the PUC and PBJS.renderAd() could get modified to drop an iframe when they detect a rendererUrl
Native already supports a rendererUrl, but it's a little different - it's associated with the AdUnit at this point, not a specific bidder. The assumption is that the script defines window.renderAd(hashOfAssets) and it knows how to get the body and resolve macros.
For display/outstream, a proposal:
- If the bidResponse contains seatbid[].bid[].ext.prebid.meta.rendererurl, PBS would store it in the cached bidresponse in PBC so it's available to the PUC.
- If the response object contains an indication that there's a special rendering scenario (rendererUrl) then instead of making an iframe and dropping the ADM, it instead goes down a different branch of code and drops an iframe something like this:
<iframe>
<script type="javascript" src="##RENDERER_URL##">
render(##BIDRESPONSEOBJECT##);
</script>
</iframe>
where BIDRESPONSEOBJECT gets resolved to a JSON object
{bidresponse: { adid: X, width: W, height: H, ... a bunch of other fields TBD ... }}
The PBC-cached bid would contain this info, but not sure if/how a webview could ask the SDK for this info for when the PUC isn't used?
discussed june 5 https://docs.google.com/document/d/1AojPDLnJzXaxwmFE5U7Qsm2GAV1fVwjcx4YyI6kX9ew/edit
@dgirardi - any thoughts on this?
Broadly I agree, but I think we need an example implementation as a first step.
The proposal is that we just standardize the interface to the bid response object format used by Prebid.js.
bid responses are not very stable, and only a portion of them is needed for rendering. Prebid currently extracts a stable subset for interaction with PUC / remote creatives and that's what I would use, except those never deal with video and I don't really know what's involved there. Hence why I think we need some real world example of an isolated video renderer to get a better idea of what data needs to go through.
@lcorrigall to work with team on developing ix bid adapter to work with a client side renderer that can render in frame, that example to be used for further work
@dgirardi and I had a discussion over slack. Here's the proposal:
The interface for the javascript file would be:
render({width, height, adUrl, config}, win)
It would be placed in the page something like this:
<iframe>
<script type="javascript" src="##RENDERER_URL##"></script>
<script>
render({width, height, adUrl, config}, window);
</script>
</iframe>
But these details may vary and shouldn't matter to the author of the renderer.
Config is renderer-specific and may cover things like display details. e.g. the Rubicon renderer would support options the publisher could set like this:
pbjs.setConfig({
rendererConfig:{
rubicon: {
closeButton: "style1",
label: "label",
collapse: false
},
ix: {
... any ix-specific parameters ...
}
}
});
To conform to this interface, the renderer javascript code would need to supply a render function that:
- reads the width, height, adUrl, and config params
- if relevant, parses the
configparams looking for its own values. Ignore others. - render
Perhaps someone familiar with renderer display details could standardize the config interface so multiple vendors could utilize the same pub-supplied config? i.e. perhaps defining a CSS style for a closeButton is something everyone does? Perhaps collapsibility is a common parameter?
@lcorrigall - any comments on the proposal here?
Looking for further input here on the proposal that Demetrio and I put on the table.
I'd add to https://github.com/prebid/Prebid.js/pull/10433#issuecomment-2362209337 that render should render into the window win, and it would be great if that window could be a safeframe (not sure about the feasibility of this).
Sorry for the delay here - The proposed interface looks good from ix's pov.
Curious if we want to keep this PR open right now.
@dgirardi should we keep this open or will the solution to #10434 be in a diff branch?