Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream merge #34

Merged
merged 35 commits into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a0194cf
ZetaGlobalSsp Analytics Adapter: keep only needed fields in event (#1…
asurovenko-zeta Feb 18, 2024
cd328b6
Add AdButler bid adapter (#11011)
trevoradbutler Feb 19, 2024
aaf2951
define split between exploratory and non exploratory sides of the det…
jbogp Feb 19, 2024
67210fa
Taboola Bid Adapter: fix cookie look up logic and gpp extracting (#1…
ahmadlob Feb 19, 2024
969e417
add schain support (#11111)
0tarof Feb 20, 2024
8cce0b8
Readpeak Bid Adapter : remove click url encoding (#11120)
readpeak-user Feb 20, 2024
7995508
fix handling of default settings for rubiconBidAdapter (#11114)
smozhaiskyi-rubi Feb 20, 2024
be08dc8
Zeta Global Ssp Adapter: remove null values from payload (#11092)
asurovenko-zeta Feb 20, 2024
0606e77
Kimberlite Bidder Adapter: initial commit (#11032)
solta-dev Feb 20, 2024
6ecd4a9
Pass TTD cookie through prebid endpoint (#11119)
wi101 Feb 20, 2024
ff58535
Euid id module: cstg opt out enforcement (#11075)
ssundahlTTD Feb 20, 2024
f58100a
Conversant Adapter: fix response handling (#11122)
johnwier Feb 20, 2024
2505322
Yieldmo Bid Adapter: send topics as string for request (#11121)
desidiver Feb 21, 2024
708bc86
Bump ip from 1.1.8 to 1.1.9 (#11124)
dependabot[bot] Feb 21, 2024
f5b71cd
Core: rendering logic overhaul, PUC-less native rendering (#10819)
dgirardi Feb 21, 2024
adbcf71
33Across User ID sub-module: Introduce first-party ID support (#10714)
macinjosh32 Feb 21, 2024
11bbd29
New bidder adapter : RixEngine (#11035)
xiaochang Feb 22, 2024
b2ace9f
change expire recommendation from 90 to 30 (#11130)
carlosfelix Feb 22, 2024
6a8f2d5
add required version (#11127)
ryohamadaumt Feb 22, 2024
9485c6b
cleanup references to allowAuctionWithoutConsent (#11129)
bretg Feb 22, 2024
e3f03be
fix video object null in validate request (#11128)
shubhamc-ins Feb 22, 2024
2c7ae8a
Reset Digital Bid Adapter: updating users syncs (#11126)
jhon-reset Feb 22, 2024
de841da
Alkimi Bid Adapter: add custom user object (#11093)
kalidas-alkimi Feb 22, 2024
ce5c5aa
Unified ID 2.0 Module: Update documentation (#11105)
mcollins-ttd Feb 23, 2024
0906043
Prebid 8.38.0 release
prebidjs-release Feb 23, 2024
8c37489
Increment version to 8.39.0-pre
prebidjs-release Feb 23, 2024
f869118
GreenbidsAnalyticsAdapter: bump version following previous PR (#11135)
jbogp Feb 23, 2024
ecc3441
Rubicon Bid Adapter: Pass on carbon segtaxes (#10985)
spotxslagle Feb 24, 2024
ab7068e
Blockthrough Bid Adapter: initial release (#10870)
PavloMalashnyak Feb 24, 2024
d93032b
OMS Adapter: add new adapter (#10924)
prBigBrother Feb 24, 2024
f2eb49b
zMaticoo Bid Adapter : add onBidWon function (#11056)
lxj15398019970 Feb 24, 2024
d607309
Criteo bid adapter: add fledge timeout and group limits (#11125)
dzhang-criteo Feb 24, 2024
cd6934c
Reset Digital Bid Adapter: usersync url (#11138)
jhon-reset Feb 24, 2024
2e40035
Yandex: use ortb2 info & Core: add webdriver flag (#11110)
chernodub Feb 24, 2024
98162dc
Logicad Bid Adapter: Add paapi support (#11123)
naru-tsujine Feb 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module.exports = {
sourceType: 'module',
ecmaVersion: 2018,
},
ignorePatterns: ['libraries/creative-renderer*'],

rules: {
'comma-dangle': 'off',
Expand Down
8 changes: 2 additions & 6 deletions allowedModules.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@

const sharedWhiteList = [
];

module.exports = {
'modules': [
...sharedWhiteList,
'criteo-direct-rsa-validate',
'crypto-js',
'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/
],
'src': [
...sharedWhiteList,
'fun-hooks/no-eval',
'just-clone',
'dlv',
'dset'
],
'libraries': [
...sharedWhiteList // empty for now, but keep it to enable linting
],
'creative': [
]
};
44 changes: 44 additions & 0 deletions creative/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Dynamic creative renderers

The contents of this directory are compiled separately from the rest of Prebid, and intended to be dynamically injected
into creative frames:

- `crossDomain.js` (compiled into `build/creative/creative.js`, also exposed in `integrationExamples/gpt/x-domain/creative.html`)
is the logic that should be statically set up in the creative.
- At build time, each folder under 'renderers' is compiled into a source string made available from a corresponding
`creative-renderer-*` library. These libraries are committed in source so that they are available to NPM consumers.
- At render time, Prebid passes the appropriate renderer's source string to the remote creative, which then runs it.

The goal is to have a creative script that is as simple, lightweight, and unchanging as possible, but still allow the possibility
of complex or frequently updated rendering logic. Compared to the approach taken by [PUC](https://github.com/prebid/prebid-universal-creative), this:

- should perform marginally better: the creative only runs logic that is pertinent (for example, it sees native logic only on native bids);
- avoids the problem of synchronizing deployments when the rendering logic is updated (see https://github.com/prebid/prebid-universal-creative/issues/187), since it's bundled together with the rest of Prebid;
- is easier to embed directly in the creative (saving a network call), since the static "shell" is designed to change as infrequently as possible;
- allows the same rendering logic to be used both in remote (cross-domain) and local (`pbjs.renderAd`) frames, since it's directly available to Prebid;
- requires Prebid.js - meaning it does not support AMP/App/Mobile (but it's still possible for something like PUC to run the same dynamic renderers
when it receives them from Prebid, and fall back to separate AMP/App/Mobile logic otherwise).

### Renderer interface

A creative renderer (not related to other types of renderers in the codebase) is a script that exposes a global `window.render` function:

```javascript
window.render = function(data, {mkFrame, sendMessage}, win) { ... }
```

where:

- `data` is rendering data about the winning bid, and varies depending on the bid type - see `getRenderingData` in `adRendering.js`;
- `mkFrame(document, attributes)` is a utility that creates a frame with the given attributes and convenient defaults (no border, margin, and scrolling);
- `sendMessage(messageType, payload)` is the mechanism by which the renderer/creative can communicate back with Prebid - see `creativeMessageHandler` in `adRendering.js`;
- `win` is the window to render into; note that this is not the same window that runs the renderer.

The function may return a promise; if it does and the promise rejects, or if the function throws, an AD_RENDER_FAILED event is emitted in Prebid. Otherwise an AD_RENDER_SUCCEEDED is fired
when the promise resolves (or when `render` returns anything other than a promise).

### Renderer development

Since renderers are compiled into source, they use production settings even during development builds. You can toggle this with
the `--creative-dev` CLI option (e.g., `gulp serve-fast --creative-dev`), which disables the minifier and generates source maps; if you do, take care
to not commit the resulting `creative-renderer-*` libraries (or run a normal build before you do).
9 changes: 9 additions & 0 deletions creative/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// eslint-disable-next-line prebid/validate-imports
import CONSTANTS from '../src/constants.json';

export const MESSAGE_REQUEST = CONSTANTS.MESSAGES.REQUEST;
export const MESSAGE_RESPONSE = CONSTANTS.MESSAGES.RESPONSE;
export const MESSAGE_EVENT = CONSTANTS.MESSAGES.EVENT;
export const EVENT_AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED;
export const EVENT_AD_RENDER_SUCCEEDED = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED;
export const ERROR_EXCEPTION = CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION;
92 changes: 92 additions & 0 deletions creative/crossDomain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {
ERROR_EXCEPTION,
EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED,
MESSAGE_EVENT,
MESSAGE_REQUEST,
MESSAGE_RESPONSE
} from './constants.js';

const mkFrame = (() => {
const DEFAULTS = {
frameBorder: 0,
scrolling: 'no',
marginHeight: 0,
marginWidth: 0,
topMargin: 0,
leftMargin: 0,
allowTransparency: 'true',
};
return (doc, attrs) => {
const frame = doc.createElement('iframe');
Object.entries(Object.assign({}, attrs, DEFAULTS))
.forEach(([k, v]) => frame.setAttribute(k, v));
return frame;
};
})();

export function renderer(win) {
return function ({adId, pubUrl, clickUrl}) {
const pubDomain = new URL(pubUrl, window.location).origin;

function sendMessage(type, payload, responseListener) {
const channel = new MessageChannel();
channel.port1.onmessage = guard(responseListener);
win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]);
}

function onError(e) {
sendMessage(MESSAGE_EVENT, {
event: EVENT_AD_RENDER_FAILED,
info: {
reason: e?.reason || ERROR_EXCEPTION,
message: e?.message
}
});
// eslint-disable-next-line no-console
e?.stack && console.error(e);
}

function guard(fn) {
return function () {
try {
return fn.apply(this, arguments);
} catch (e) {
onError(e);
}
};
}

function onMessage(ev) {
let data;
try {
data = JSON.parse(ev.data);
} catch (e) {
return;
}
if (data.message === MESSAGE_RESPONSE && data.adId === adId) {
const renderer = mkFrame(win.document, {
width: 0,
height: 0,
style: 'display: none',
srcdoc: `<script>${data.renderer}</script>`
});
renderer.onload = guard(function () {
const W = renderer.contentWindow;
// NOTE: on Firefox, `Promise.resolve(P)` or `new Promise((resolve) => resolve(P))`
// does not appear to work if P comes from another frame
W.Promise.resolve(W.render(data, {sendMessage, mkFrame}, win)).then(
() => sendMessage(MESSAGE_EVENT, {event: EVENT_AD_RENDER_SUCCEEDED}),
onError
)
});
win.document.body.appendChild(renderer);
}
}

sendMessage(MESSAGE_REQUEST, {
options: {clickUrl}
}, onMessage);
};
}

window.pbRender = renderer(window);
4 changes: 4 additions & 0 deletions creative/renderers/display/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// eslint-disable-next-line prebid/validate-imports
import CONSTANTS from '../../../src/constants.json';

export const ERROR_NO_AD = CONSTANTS.AD_RENDER_FAILED_REASON.NO_AD;
21 changes: 21 additions & 0 deletions creative/renderers/display/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {ERROR_NO_AD} from './constants.js';

export function render({ad, adUrl, width, height}, {mkFrame}, win) {
if (!ad && !adUrl) {
throw {
reason: ERROR_NO_AD,
message: 'Missing ad markup or URL'
};
} else {
const doc = win.document;
const attrs = {width, height};
if (adUrl && !ad) {
attrs.src = adUrl;
} else {
attrs.srcdoc = ad;
}
doc.body.appendChild(mkFrame(doc, attrs));
}
}

window.render = render;
14 changes: 14 additions & 0 deletions creative/renderers/native/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// eslint-disable-next-line prebid/validate-imports
import CONSTANTS from '../../../src/constants.json';

export const MESSAGE_NATIVE = CONSTANTS.MESSAGES.NATIVE;
export const ACTION_RESIZE = 'resizeNativeHeight';
export const ACTION_CLICK = 'click';
export const ACTION_IMP = 'fireNativeImpressionTrackers';

export const ORTB_ASSETS = {
title: 'text',
data: 'value',
img: 'url',
video: 'vasttag'
}
88 changes: 88 additions & 0 deletions creative/renderers/native/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {ACTION_CLICK, ACTION_IMP, ACTION_RESIZE, MESSAGE_NATIVE, ORTB_ASSETS} from './constants.js';

export function getReplacer(adId, {assets = [], ortb, nativeKeys = {}}) {
const assetValues = Object.fromEntries((assets).map(({key, value}) => [key, value]));
let repl = Object.fromEntries(
Object.entries(nativeKeys).flatMap(([name, key]) => {
const value = assetValues.hasOwnProperty(name) ? assetValues[name] : undefined;
return [
[`##${key}##`, value],
[`${key}:${adId}`, value]
];
})
);
if (ortb) {
Object.assign(repl,
{
'##hb_native_linkurl##': ortb.link?.url,
'##hb_native_privacy##': ortb.privacy
},
Object.fromEntries(
(ortb.assets || []).flatMap(asset => {
const type = Object.keys(ORTB_ASSETS).find(type => asset[type]);
return [
type && [`##hb_native_asset_id_${asset.id}##`, asset[type][ORTB_ASSETS[type]]],
asset.link?.url && [`##hb_native_asset_link_id_${asset.id}##`, asset.link.url]
].filter(e => e);
})
)
);
}
repl = Object.entries(repl).concat([[/##hb_native_asset_(link_)?id_\d+##/g]]);

return function (template) {
return repl.reduce((text, [pattern, value]) => text.replaceAll(pattern, value || ''), template);
};
}

function loadScript(url, doc) {
return new Promise((resolve, reject) => {
const script = doc.createElement('script');
script.onload = resolve;
script.onerror = reject;
script.src = url;
doc.body.appendChild(script);
});
}

export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) {
const {rendererUrl, assets, ortb, adTemplate} = nativeData;
const doc = win.document;
if (rendererUrl) {
return load(rendererUrl, doc).then(() => {
if (typeof win.renderAd !== 'function') {
throw new Error(`Renderer from '${rendererUrl}' does not define renderAd()`);
}
const payload = assets || [];
payload.ortb = ortb;
return win.renderAd(payload);
});
} else {
return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML));
}
}

export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) {
const {head, body} = win.document;
const resize = () => sendMessage(MESSAGE_NATIVE, {
action: ACTION_RESIZE,
height: body.offsetHeight,
width: body.offsetWidth
});
const replacer = getReplacer(adId, native);
head && (head.innerHTML = replacer(head.innerHTML));
return getMarkup(adId, native, replacer, win).then(markup => {
body.innerHTML = markup;
if (typeof win.postRenderAd === 'function') {
win.postRenderAd({adId, ...native});
}
win.document.querySelectorAll('.pb-click').forEach(el => {
const assetId = el.getAttribute('hb_native_asset_id');
el.addEventListener('click', () => sendMessage(MESSAGE_NATIVE, {action: ACTION_CLICK, assetId}));
});
sendMessage(MESSAGE_NATIVE, {action: ACTION_IMP});
win.document.readyState === 'complete' ? resize() : win.onload = resize;
});
}

window.render = render;
Loading