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

Emit AD_RENDER_SUCCEEDED and AD_RENDER_FAILED for native ads #199

Merged
merged 3 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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',
patmmccann marked this conversation as resolved.
Show resolved Hide resolved
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