Skip to content

Commit

Permalink
Merge branch 'prebid:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
BaronJHYu authored Dec 1, 2023
2 parents 1847dcf + ec42b13 commit e89124f
Show file tree
Hide file tree
Showing 91 changed files with 5,702 additions and 2,159 deletions.
27 changes: 22 additions & 5 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,20 @@ function makeWebpackPkg(extraConfig = {}) {
}
}

function buildCreative() {
return gulp.src(['**/*'])
.pipe(webpackStream(require('./webpack.creative.js')))
.pipe(gulp.dest('build/creative'))
}

function updateCreativeExample(cb) {
const CREATIVE_EXAMPLE = 'integrationExamples/gpt/x-domain/creative.html';
const root = require('node-html-parser').parse(fs.readFileSync(CREATIVE_EXAMPLE));
root.querySelectorAll('script')[0].textContent = fs.readFileSync('build/creative/creative.js')
fs.writeFileSync(CREATIVE_EXAMPLE, root.toString())
cb();
}

function getModulesListToAddInBanner(modules) {
if (!modules || modules.length === helpers.getModuleNames().length) {
return 'All available modules for this version.'
Expand Down Expand Up @@ -405,6 +419,7 @@ function watchTaskMaker(options = {}) {
return function watch(done) {
var mainWatcher = gulp.watch([
'src/**/*.js',
'libraries/**/*.js',
'modules/**/*.js',
].concat(options.alsoWatch));

Expand All @@ -415,8 +430,8 @@ function watchTaskMaker(options = {}) {
}
}

const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))});
const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')});
const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test, buildCreative))});
const watchFast = watchTaskMaker({livereload: false, task: () => gulp.parallel('build-bundle-dev', buildCreative)});

// support tasks
gulp.task(lint);
Expand Down Expand Up @@ -447,21 +462,23 @@ gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({
}
}), gulpBundle.bind(null, false)));

gulp.task('build-creative', gulp.series(buildCreative, updateCreativeExample));

// public tasks (dependencies are needed for each task since they can be ran on their own)
gulp.task('test-only', test);
gulp.task('test-all-features-disabled', testTaskMaker({disableFeatures: require('./features.json'), oneBrowser: 'chrome', watch: false}));
gulp.task('test', gulp.series(clean, lint, gulp.series('test-all-features-disabled', 'test-only')));
gulp.task('test', gulp.series(clean, lint, gulp.parallel('build-creative', gulp.series('test-all-features-disabled', 'test-only'))));

gulp.task('test-coverage', gulp.series(clean, testCoverage));
gulp.task(viewCoverage);

gulp.task('coveralls', gulp.series('test-coverage', coveralls));

gulp.task('build', gulp.series(clean, 'build-bundle-prod'));
gulp.task('build', gulp.series(clean, 'build-bundle-prod', 'build-creative'));
gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid));

gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test)));
gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast)));
gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', buildCreative, watchFast)));
gulp.task('serve-prod', gulp.series(clean, gulp.parallel('build-bundle-prod', startLocalServer)));
gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true}))));
gulp.task('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer)));
Expand Down
108 changes: 8 additions & 100 deletions integrationExamples/gpt/x-domain/creative.html
Original file line number Diff line number Diff line change
@@ -1,105 +1,13 @@
<script>
// this script can be returned by an ad server delivering a cross domain iframe, into which the
// creative will be rendered, e.g. GAM delivering a SafeFrame

const windowLocation = window.location;
const urlParser = document.createElement('a');
urlParser.href = '%%PATTERN:url%%';
const publisherDomain = urlParser.protocol + '//' + urlParser.hostname;
const adId = '%%PATTERN:hb_adid%%';

function receiveMessage(ev) {
const origin = ev.origin || ev.originalEvent.origin;
if (origin === publisherDomain) {
renderAd(ev);
}
}

function renderAd(ev) {
const key = ev.message ? 'message' : 'data';
let adObject = {};
try {
adObject = JSON.parse(ev[key]);
} catch (e) {
return;
}

if (adObject.message && adObject.message === 'Prebid Response' &&
adObject.adId === adId) {
try {
const body = window.document.body;
const ad = adObject.ad;
const url = adObject.adUrl;
const width = adObject.width;
const height = adObject.height;

if (adObject.mediaType === 'video') {
signalRenderResult(false, {
reason: 'preventWritingOnMainDocument',
message: `Cannot render video ad ${adId}`
});
console.log('Error trying to write ad.');
} else if (ad) {
const frame = document.createElement('iframe');
frame.setAttribute('FRAMEBORDER', 0);
frame.setAttribute('SCROLLING', 'no');
frame.setAttribute('MARGINHEIGHT', 0);
frame.setAttribute('MARGINWIDTH', 0);
frame.setAttribute('TOPMARGIN', 0);
frame.setAttribute('LEFTMARGIN', 0);
frame.setAttribute('ALLOWTRANSPARENCY', 'true');
frame.setAttribute('width', width);
frame.setAttribute('height', height);
body.appendChild(frame);
frame.contentDocument.open();
frame.contentDocument.write(ad);
frame.contentDocument.close();
signalRenderResult(true);
} else if (url) {
body.insertAdjacentHTML('beforeend', '<IFRAME SRC="' + url + '" FRAMEBORDER="0" SCROLLING="no" MARGINHEIGHT="0" MARGINWIDTH="0" TOPMARGIN="0" LEFTMARGIN="0" ALLOWTRANSPARENCY="true" WIDTH="' + width + '" HEIGHT="' + height + '"></IFRAME>');
signalRenderResult(true);
} else {
signalRenderResult(false, {
reason: 'noAd',
message: `No ad for ${adId}`
});
console.log(`Error trying to write ad. No ad markup or adUrl for ${adId}`);
}
} catch (e) {
signalRenderResult(false, {reason: 'exception', message: e.message});
console.log(`Error in rendering ad`, e);
}
}

function signalRenderResult(success, {reason, message} = {}) {
const payload = {
message: 'Prebid Event',
adId,
event: success ? 'adRenderSucceeded' : 'adRenderFailed',
}
if (!success) {
payload.info = {reason, message};
}
window.parent.postMessage(JSON.stringify(payload), publisherDomain);
}
// this code is autogenerated, also available in 'build/creative/creative.js'
<script>!function(){"use strict";var e=JSON.parse('{"FP":{"h0":"adRenderFailed","gV":"adRenderSucceeded"},"q_":{"Ex":"noAd","XW":"exception"}}');const t=e.FP.gV,n=e.FP.h0,r=e.q_.Ex,s=e.q_.XW,a={frameBorder:0,scrolling:"no",marginHeight:0,marginWidth:0,topMargin:0,leftMargin:0,allowTransparency:"true"};function i(e,t){const n=e.createElement("iframe");return t=Object.assign({},t,a),Object.entries(t).forEach((([e,t])=>n.setAttribute(e,t))),e.body.appendChild(n),n}window.renderAd=function(e=window){return function({adId:a,pubUrl:o,clickUrl:c}){const d=function(){const t=e.document.createElement("a");return t.href=o,t.protocol+"//"+t.host}();function g(t,n,r){e.parent.postMessage(JSON.stringify(Object.assign({message:t,adId:a},n)),d,r)}function u(e){g("Prebid Event",{event:null==e?t:n,info:e})}function h(t){let n={};try{n=JSON.parse(t[t.message?"message":"data"])}catch(e){return}if("Prebid Response"===n.message&&n.adId===a)try{let t=e.document;n.ad&&(t=i(t,{width:n.width,height:n.height}).contentDocument,t.open()),function({ad:e,adUrl:t,width:n,height:s},a,o=document){e||t?(t&&!e?i(o,{width:n,height:s,src:t}):(o.write(e),o.close()),a()):a({reason:r,message:"Missing ad markup or URL"})}(n,u,t)}catch(e){u({reason:s,message:e.message})}}const l=new MessageChannel;l.port1.onmessage=h,g("Prebid Request",{options:{clickUrl:c}},[l.port2]),e.addEventListener("message",h,!1)}}()}();</script>

}


function requestAdFromPrebid() {
const message = JSON.stringify({
message: 'Prebid Request',
adId
});
const channel = new MessageChannel();
channel.port1.onmessage = renderAd;
window.parent.postMessage(message, publisherDomain, [channel.port2]);
}

function listenAdFromPrebid() {
window.addEventListener('message', receiveMessage, false);
}

listenAdFromPrebid();
requestAdFromPrebid();
<script>
renderAd({
adId: '%%PATTERN:hb_adid%%',
pubUrl: '%%PATTERN:url%%',
clickUrl: '%%CLICK_URL_UNESC%%'
});
</script>
10 changes: 10 additions & 0 deletions libraries/creativeRender/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import events from '../../src/constants.json';

export const PREBID_NATIVE = 'Prebid Native';
export const PREBID_REQUEST = 'Prebid Request';
export const PREBID_RESPONSE = 'Prebid Response';
export const PREBID_EVENT = 'Prebid Event';
export const AD_RENDER_SUCCEEDED = events.EVENTS.AD_RENDER_SUCCEEDED;
export const AD_RENDER_FAILED = events.EVENTS.AD_RENDER_FAILED;
export const NO_AD = events.AD_RENDER_FAILED_REASON.NO_AD;
export const EXCEPTION = events.AD_RENDER_FAILED_REASON.EXCEPTION;
57 changes: 57 additions & 0 deletions libraries/creativeRender/crossDomain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {mkFrame, writeAd} from './writer.js';
import {
AD_RENDER_FAILED,
AD_RENDER_SUCCEEDED,
PREBID_EVENT,
PREBID_RESPONSE,
PREBID_REQUEST,
EXCEPTION
} from './constants.js';

export function renderer(win = window) {
return function ({adId, pubUrl, clickUrl}) {
const pubDomain = (function() {
const a = win.document.createElement('a');
a.href = pubUrl;
return a.protocol + '//' + a.host;
})();
function sendMessage(type, payload, transfer) {
win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, transfer);
}
function cb(err) {
sendMessage(PREBID_EVENT, {
event: err == null ? AD_RENDER_SUCCEEDED : AD_RENDER_FAILED,
info: err
});
}
function onMessage(ev) {
let data = {};
try {
data = JSON.parse(ev[ev.message ? 'message' : 'data']);
} catch (e) {
return;
}
if (data.message === PREBID_RESPONSE && data.adId === adId) {
try {
let doc = win.document
if (data.ad) {
doc = mkFrame(doc, {width: data.width, height: data.height}).contentDocument;
doc.open();
}
writeAd(data, cb, doc);
} catch (e) {
// eslint-disable-next-line standard/no-callback-literal
cb({ reason: EXCEPTION, message: e.message })
}
}
}

const channel = new MessageChannel();
channel.port1.onmessage = onMessage;
sendMessage(PREBID_REQUEST, {
options: {clickUrl}
}, [channel.port2]);
win.addEventListener('message', onMessage, false);
}
}
window.renderAd = renderer();
62 changes: 62 additions & 0 deletions libraries/creativeRender/direct.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {emitAdRenderFail, emitAdRenderSucceeded, handleRender} from '../../src/adRendering.js';
import {writeAd} from './writer.js';
import {auctionManager} from '../../src/auctionManager.js';
import CONSTANTS from '../../src/constants.json';
import {inIframe, insertElement} from '../../src/utils.js';
import {getGlobal} from '../../src/prebidGlobal.js';
import {EXCEPTION} from './constants.js';

export function renderAdDirect(doc, adId, options) {
let bid;
function cb(err) {
if (err != null) {
emitAdRenderFail(Object.assign({id: adId, bid}, err));
} else {
emitAdRenderSucceeded({doc, bid, adId})
}
}
function renderFn(adData) {
writeAd(adData, cb, doc);
if (doc.defaultView && doc.defaultView.frameElement) {
doc.defaultView.frameElement.width = adData.width;
doc.defaultView.frameElement.height = adData.height;
}
// TODO: this is almost certainly the wrong way to do this
const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`);
insertElement(creativeComment, doc, 'html');
}
try {
if (!adId || !doc) {
// eslint-disable-next-line standard/no-callback-literal
cb({
reason: CONSTANTS.AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID,
message: `missing ${adId ? 'doc' : 'adId'}`
});
} else {
bid = auctionManager.findBidByAdId(adId);

if (FEATURES.VIDEO) {
// TODO: could the video module implement this as a custom renderer, rather than a special case in here?
const adUnit = bid && auctionManager.index.getAdUnit(bid);
const videoModule = getGlobal().videoModule;
if (adUnit?.video && videoModule) {
videoModule.renderBid(adUnit.video.divId, bid);
return;
}
}

if ((doc === document && !inIframe())) {
// eslint-disable-next-line standard/no-callback-literal
cb({
reason: CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT,
message: `renderAd was prevented from writing to the main document.`
})
} else {
handleRender(renderFn, {adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid});
}
}
} catch (e) {
// eslint-disable-next-line standard/no-callback-literal
cb({reason: EXCEPTION, message: e.message})
}
}
34 changes: 34 additions & 0 deletions libraries/creativeRender/writer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {NO_AD} from './constants.js';

const IFRAME_ATTRS = {
frameBorder: 0,
scrolling: 'no',
marginHeight: 0,
marginWidth: 0,
topMargin: 0,
leftMargin: 0,
allowTransparency: 'true',
};

export function mkFrame(doc, attrs) {
const frame = doc.createElement('iframe');
attrs = Object.assign({}, attrs, IFRAME_ATTRS);
Object.entries(attrs).forEach(([k, v]) => frame.setAttribute(k, v));
doc.body.appendChild(frame);
return frame;
}

export function writeAd({ad, adUrl, width, height}, cb, doc = document) {
if (!ad && !adUrl) {
// eslint-disable-next-line standard/no-callback-literal
cb({reason: NO_AD, message: 'Missing ad markup or URL'});
} else {
if (adUrl && !ad) {
mkFrame(doc, {width, height, src: adUrl})
} else {
doc.write(ad);
doc.close();
}
cb();
}
}
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"blueconicRtdProvider",
"brandmetricsRtdProvider",
"browsiRtdProvider",
"cleanioRtdProvider",
"captifyRtdProvider",
"mediafilterRtdProvider",
"confiantRtdProvider",
"dgkeywordRtdProvider",
"experianRtdProvider",
Expand Down
Loading

0 comments on commit e89124f

Please sign in to comment.