Prebid.js icon indicating copy to clipboard operation
Prebid.js copied to clipboard

PBjs Core - OrtbConverter : dynamic renderer support

Open ccorbo opened this issue 2 years ago • 23 comments

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 avatar Sep 05 '23 20:09 ccorbo

@ccorbo we're unblocked now!

patmmccann avatar Nov 14 '23 19:11 patmmccann

@patmmccann updated the PR this is ready for review

ccorbo avatar Feb 13 '24 16:02 ccorbo

LGTM! @lksharma or @dgirardi please take a second look

patmmccann avatar Feb 24 '24 04:02 patmmccann

@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)
     ```
     

ccorbo avatar Apr 10 '24 18:04 ccorbo

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:

  1. adUnit.mediaTypes.(video|banner|native).renderer
  2. bid.renderer
  3. adUnit.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.

dgirardi avatar Apr 17 '24 20:04 dgirardi

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."

bretg avatar Apr 22 '24 19:04 bretg

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).

dgirardi avatar Apr 22 '24 20:04 dgirardi

Just noticed that the path is off here -- instead of seatbid[].bid[].ext.rendererurl, I think it's seatbid[].bid[].ext.prebid.meta.rendererurl

bretg avatar May 15 '24 15:05 bretg

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:

  1. The URL returned in seatbid[].bid[].ext.prebid.meta.rendererurl must point to a javascript file.
  2. That javascript file must contain a function called "render()" that takes an adId.
  3. 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>
  1. 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.
  2. Prebid-core responds to the postMessage, perhaps with the same logic currently done for PUC?
  3. 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.

bretg avatar May 15 '24 15:05 bretg

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).

dgirardi avatar May 15 '24 16:05 dgirardi

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.

bretg avatar May 15 '24 16:05 bretg

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).

dgirardi avatar May 15 '24 17:05 dgirardi

width / height? burl / nurl ?

Ok then, pass the whole bidresponse object and let them figure it out.

bretg avatar May 15 '24 17:05 bretg

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:

  1. 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.
  2. 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?

bretg avatar May 20 '24 15:05 bretg

discussed june 5 https://docs.google.com/document/d/1AojPDLnJzXaxwmFE5U7Qsm2GAV1fVwjcx4YyI6kX9ew/edit

patmmccann avatar Jun 05 '24 16:06 patmmccann

@dgirardi - any thoughts on this?

bretg avatar Jul 08 '24 20:07 bretg

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.

dgirardi avatar Jul 08 '24 23:07 dgirardi

@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

patmmccann avatar Jul 16 '24 14:07 patmmccann

@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 config params 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?

bretg avatar Sep 19 '24 21:09 bretg

@lcorrigall - any comments on the proposal here?

bretg avatar Nov 13 '24 19:11 bretg

Looking for further input here on the proposal that Demetrio and I put on the table.

bretg avatar Dec 20 '24 15:12 bretg

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).

dgirardi avatar Feb 12 '25 17:02 dgirardi

Sorry for the delay here - The proposed interface looks good from ix's pov.

ccorbo avatar Feb 12 '25 21:02 ccorbo

Curious if we want to keep this PR open right now.

zachsavishinsky avatar Oct 03 '25 14:10 zachsavishinsky

@dgirardi should we keep this open or will the solution to #10434 be in a diff branch?

patmmccann avatar Oct 20 '25 17:10 patmmccann