Skip to content

Commit

Permalink
Emit AD_RENDER_SUCCEEDED and AD_RENDER_FAILED for native ads (#199)
Browse files Browse the repository at this point in the history
* Emit AD_RENDER_SUCCEEDED and AD_RENDER_FAILED for native ads

* Merge master
  • Loading branch information
dgirardi authored Sep 7, 2023
1 parent 8cbe9b1 commit 52e066a
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 297 deletions.
10 changes: 3 additions & 7 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function clean() {

/**
* This generic function will compile the file specified as inputFile and will
* generate an output file in the build directory.
* generate an output file in the build directory.
*/
function buildDev({ inputFile, outputFile }) {
var cloned = _.cloneDeep(webpackConfig);
Expand Down Expand Up @@ -234,7 +234,7 @@ function newKarmaCallback(done) {
process.exit(exitCode);
}
}
}
}
}

function setupE2E(done) {
Expand Down Expand Up @@ -265,11 +265,7 @@ function watch(done) {
done();
}

function openWebPage() {
return opens(`${(argv.https) ? 'https' : 'http'}://localhost:${port}`);
}

gulp.task('serve', gulp.series(clean, gulp.parallel(...buildDevFunctions, watch, test), openWebPage));
gulp.task('serve', gulp.series(clean, gulp.parallel(...buildDevFunctions, watch, test)));

gulp.task('build-dev', gulp.parallel(...buildDevFunctions));

Expand Down
10 changes: 4 additions & 6 deletions karma.conf.maker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const webpackConf = require('./webpack.conf');
const karmaConstants = require('karma').constants;
const path = require('path');

function setBrowsers(karmaConf, browserstack, watchMode) {
function setBrowsers(karmaConf, browserstack) {
if (browserstack) {
karmaConf.browserStack = {
username: process.env.BROWSERSTACK_USERNAME,
Expand All @@ -12,8 +12,6 @@ function setBrowsers(karmaConf, browserstack, watchMode) {
}
karmaConf.customLaunchers = require('./browsers.json')
karmaConf.browsers = Object.keys(karmaConf.customLaunchers);
} else if (watchMode) {
karmaConf.browsers = ['Chrome'];
}
}

Expand All @@ -29,7 +27,7 @@ function setReporters(karmaConf, codeCoverage, browserstack) {
suppressPassed: true
};
}

if (codeCoverage) {
karmaConf.reporters.push('coverage-istanbul');
karmaConf.coverageIstanbulReporter = {
Expand All @@ -41,7 +39,7 @@ function setReporters(karmaConf, codeCoverage, browserstack) {
urlFriendlyName: true, // simply replaces spaces with _ for files/dirs
}
}
}
}
}
}

Expand Down Expand Up @@ -149,6 +147,6 @@ module.exports = function(codeCoverage, browserstack, watchMode) {
captureTimeout: 4 * 60 * 1000, // default 60000
}
setReporters(config, codeCoverage, browserstack);
setBrowsers(config, browserstack, watchMode);
setBrowsers(config, browserstack);
return config;
}
123 changes: 68 additions & 55 deletions src/nativeAssetManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,18 @@ const CLICK_URL_UNESC = `%%CLICK_URL_UNESC%%`;

let clickUrlUnesc = '';

export function newNativeAssetManager(win, nativeTag) {
const { pubUrl } = nativeTag;
export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessenger) {


// clickUrlUnesc contains the url to track clicks in GAM. we check if it
// has been transformed, by GAM, in an URL.
// if CLICK_URL_UNESC is the string "%%CLICK_URL_UNESC%%", we're not in GAM.
// clickUrlUnesc contains the url to track clicks in GAM. we check if it
// has been transformed, by GAM, in an URL.
// if CLICK_URL_UNESC is the string "%%CLICK_URL_UNESC%%", we're not in GAM.
if (nativeTag.clickUrlUnesc && nativeTag.clickUrlUnesc !== CLICK_URL_UNESC) {
clickUrlUnesc = nativeTag.clickUrlUnesc;
}
const {pubUrl} = nativeTag;

const sendMessage = prebidMessenger(pubUrl, win);
let callback;
const sendMessage = mkMessenger(pubUrl, win);
let callback, errCallback;
let errorCountEscapeHatch = 0;
let cancelMessageListener;

Expand Down Expand Up @@ -197,7 +196,8 @@ export function newNativeAssetManager(win, nativeTag) {
* and requestAllAssets flag is set in the tag, postmessage roundtrip
* to retrieve native assets that have a value on the corresponding bid
*/
function loadAssets(adId, cb) {
function loadAssets(adId, cb, onError) {
errCallback = onError;
const placeholders = scanDOMForPlaceHolders(adId);

if (hasPbNativeData() && win.pbNativeData.hasOwnProperty('assetsToReplace')) {
Expand All @@ -213,6 +213,8 @@ export function newNativeAssetManager(win, nativeTag) {
} else if (placeholders.length > 0) {
callback = cb;
cancelMessageListener = requestAssets(adId, placeholders);
} else {
onError && onError(new Error('No assets to load: no placeholders found in template'));
}
}

Expand Down Expand Up @@ -287,72 +289,83 @@ export function newNativeAssetManager(win, nativeTag) {
* Postmessage listener for when Prebid responds with requested native assets.
*/
function replaceAssets(event) {
var data = {};

try {
data = JSON.parse(event.data);
} catch (e) {
if (errorCountEscapeHatch++ > 10) {
/*
* if for some reason Prebid never responds with the native assets,
* get rid of this listener because other messages won't stop coming
*/
stopListening();

var data = {};

try {
data = JSON.parse(event.data);
} catch (e) {
if (errorCountEscapeHatch++ > 10) {
// TODO: this should be a timeout, not an arbitrary cap on the number of messages received
/*
* if for some reason Prebid never responds with the native assets,
* get rid of this listener because other messages won't stop coming
*/
stopListening();
throw e;
}
return;
}
return;
}

if (data.message === 'assetResponse') {
// add GAM %%CLICK_URL_UNESC%% to the data object to be eventually used in renderers
data.clickUrlUnesc = clickUrlUnesc;

const body = win.document.body.innerHTML;
const head = win.document.head.innerHTML;

if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;
if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;

if (head) win.document.head.innerHTML = replace(head, data);
callback = ((cb) => {
return () => {
fireNativeImpressionTrackers(data.adId, sendMessage);
addNativeClickTrackers(data.adId, sendMessage);
cb && cb();
}
})(callback);

data.assets = data.assets || [];
let renderPayload = data.assets;
if (data.ortb) {
renderPayload.ortb = data.ortb;
callback = () => {
fireNativeImpressionTrackers(data.adId, sendMessage);
addNativeClickTrackers(data.adId, data.ortb, sendMessage);
}
}
if (head) win.document.head.innerHTML = replace(head, data);

if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
if (win.renderAd) {
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';

renderAd(newHtml, data);
} else if (document.getElementById('pb-native-renderer')) {
document.getElementById('pb-native-renderer').addEventListener('load', function() {
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
data.assets = data.assets || [];
let renderPayload = data.assets;
if (data.ortb) {
renderPayload.ortb = data.ortb;
}

renderAd(newHtml, data);
});
} else {
loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function() {
if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
if (win.renderAd) {
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';

renderAd(newHtml, data);
})
}
} else if ((data.hasOwnProperty('adTemplate') && data.adTemplate)||(hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
const newHtml = replace(template, data);
} else if (document.getElementById('pb-native-renderer')) {
document.getElementById('pb-native-renderer').addEventListener('load', function () {
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';

renderAd(newHtml, data);
});
} else {
loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function () {
const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';

renderAd(newHtml, data);
});
}
} else if ((data.hasOwnProperty('adTemplate') && data.adTemplate) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
const newHtml = replace(template, data);

renderAd(newHtml, data);
} else {
const newHtml = replace(body, data);
renderAd(newHtml, data);
} else {
const newHtml = replace(body, data);

win.document.body.innerHTML = newHtml;
callback && callback();
stopListening();
win.document.body.innerHTML = newHtml;
callback && callback();
stopListening();
}
}
} catch (e) {
errCallback && errCallback(e);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/nativeORTBTrackerManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export function fireNativeImpressionTrackers(adId, sendMessage) {
sendMessage(message);
}

export function addNativeClickTrackers(adId, ortb, sendMessage) {
export function addNativeClickTrackers(adId, sendMessage) {
const message = {
message: 'Prebid Native',
action: 'click',
adId
adId
};
const adElements = document.getElementsByClassName(AD_ANCHOR_CLASS_NAME) || [];
// get all assets that have 'link' property, map asset.id -> asset.link
Expand Down
117 changes: 47 additions & 70 deletions src/nativeRenderManager.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,53 @@
/*
* Script to handle firing impression and click trackers from native teamplates
*/
import { newNativeAssetManager } from './nativeAssetManager';
import {newNativeAssetManager} from './nativeAssetManager';
import {prebidMessenger} from './messaging.js';

const AD_ANCHOR_CLASS_NAME = 'pb-click';
const AD_DATA_ADID_ATTRIBUTE = 'pbAdId';

export function newNativeRenderManager(win) {
let sendMessage;


function findAdElements(className) {
let adElements = win.document.getElementsByClassName(className);
return adElements || [];
}

function loadClickTrackers(event, adId) {
fireTracker(adId, 'click');
}

function fireTracker(adId, action) {
if (adId === '') {
console.warn('Prebid tracking event was missing \'adId\'. Was adId macro set in the HTML attribute ' + AD_DATA_ADID_ATTRIBUTE + 'on the ad\'s anchor element');
} else {
let message = { message: 'Prebid Native', adId: adId };

// fires click trackers when called via link
if (action === 'click') {
message.action = 'click';
}
sendMessage(message);
}
}

function fireNativeImpTracker(adId) {
fireTracker(adId, 'impression');
}

function fireNativeCallback() {
const adElements = findAdElements(AD_ANCHOR_CLASS_NAME);
for (let i = 0; i < adElements.length; i++) {
adElements[i].addEventListener('click', function(event) {
loadClickTrackers(event, window.pbNativeData.adId);
}, true);
}
}

// START OF MAIN CODE
let renderNativeAd = function(doc, nativeTag) {
window.pbNativeData = nativeTag;
sendMessage = prebidMessenger(nativeTag.pubUrl, win);
const nativeAssetManager = newNativeAssetManager(window, nativeTag);

if (nativeTag.hasOwnProperty('adId')) {

if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
const scr = doc.createElement('SCRIPT');
scr.src = nativeTag.rendererUrl,
scr.id = 'pb-native-renderer';
doc.body.appendChild(scr);
}
nativeAssetManager.loadAssets(nativeTag.adId, () => {
fireNativeImpTracker(nativeTag.adId);
fireNativeCallback();
});
} else {
console.warn("Prebid Native Tag object was missing 'adId'.");
}
}

return {
renderNativeAd
}
export function newNativeRenderManager(win, mkMessenger = prebidMessenger, assetMgr = newNativeAssetManager) {
let sendMessage;


let renderNativeAd = function (doc, nativeTag) {
window.pbNativeData = nativeTag;
sendMessage = mkMessenger(nativeTag.pubUrl, win);

function signalResult(adId, success, info) {
sendMessage({
message: 'Prebid Event',
adId,
event: success ? 'adRenderSucceeded' : 'adRenderFailed',
info
});
}

try {
const nativeAssetManager = assetMgr(window, nativeTag);

if (nativeTag.adId != null) {

if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
const scr = doc.createElement('SCRIPT');
scr.src = nativeTag.rendererUrl,
scr.id = 'pb-native-renderer';
doc.body.appendChild(scr);
}
nativeAssetManager.loadAssets(nativeTag.adId, () => {
signalResult(nativeTag.adId, true);
}, (e) => {
signalResult(nativeTag.adId, false, {reason: 'exception', message: e.message});
});
} else {
signalResult(null, false, {reason: 'missingDocOrAdid'});
console.warn('Prebid Native Tag object was missing \'adId\'.');
}
} catch (e) {
signalResult(nativeTag && nativeTag.adId, false, {reason: 'exception', message: e.message});
console.error('Error rendering ad', e);
}
};

return {
renderNativeAd
};
}
Loading

0 comments on commit 52e066a

Please sign in to comment.