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

PUC update to include Native Phase 2 spec: #106

Merged
merged 16 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from 9 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
29 changes: 25 additions & 4 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ function buildNativeDev() {
.pipe(gulp.dest('build'));
}

function buildNativeRenderDev() {
var cloned = _.cloneDeep(webpackConfig);
cloned.output.filename = 'native-render.js';

return gulp.src(['src/nativeRender.js'])
.pipe(webpackStream(cloned))
.pipe(gulp.dest('build'));
}

function buildCookieSync() {
let cloned = _.cloneDeep(webpackConfig);
delete cloned.devtool;
Expand Down Expand Up @@ -103,6 +112,18 @@ function buildNative() {
.pipe(gulp.dest('dist'));
}

function buildNativeRender() {
var cloned = _.cloneDeep(webpackConfig);
delete cloned.devtool;
cloned.output.filename = 'native-render.js';

return gulp.src(['src/nativeRender.js'])
.pipe(webpackStream(cloned))
.pipe(uglify())
.pipe(header('/* v<%= creative.version %>\n' + dateString + ' */\n', { creative: creative }))
.pipe(gulp.dest('dist'));
}

function buildUid() {
var cloned = _.cloneDeep(webpackConfig);
delete cloned.devtool;
Expand Down Expand Up @@ -160,7 +181,7 @@ function setupE2E(done) {

gulp.task('test', gulp.series(clean, test));

gulp.task('e2e-test', gulp.series(clean, setupE2E, gulp.parallel(buildDev, buildCookieSync, buildNativeDev, buildUidDev, watch), test));
gulp.task('e2e-test', gulp.series(clean, setupE2E, gulp.parallel(buildDev, buildCookieSync, buildNativeDev, buildNativeRenderDev, buildUidDev, watch), test));

function watch(done) {
const mainWatcher = gulp.watch([
Expand All @@ -175,17 +196,17 @@ function watch(done) {
root: './'
});

mainWatcher.on('all', gulp.series(clean, gulp.parallel(buildDev, buildNativeDev, buildCookieSync, buildUidDev), test));
mainWatcher.on('all', gulp.series(clean, gulp.parallel(buildDev, buildNativeDev, buildNativeRenderDev, buildCookieSync, buildUidDev), test));
done();
}

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

gulp.task('serve', gulp.series(clean, gulp.parallel(buildDev, buildNativeDev, buildCookieSync, buildUidDev, watch, test), openWebPage));
gulp.task('serve', gulp.series(clean, gulp.parallel(buildDev, buildNativeDev, buildNativeRenderDev, buildCookieSync, buildUidDev, watch, test), openWebPage));

gulp.task('build', gulp.parallel(buildProd, buildCookieSync, buildNative, buildUid));
gulp.task('build', gulp.parallel(buildProd, buildCookieSync, buildNative, buildNativeRender, buildUid));

gulp.task('test-coverage', (done) => {
new KarmaServer(karmaConfMaker(true, false, false), newKarmaCallback(done)).start();
Expand Down
98 changes: 88 additions & 10 deletions src/nativeAssetManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* values in native creative templates.
*/

import { sendRequest } from './utils';
import { sendRequest, loadScript } from './utils';

/*
* Native asset->key mapping from Prebid.js/src/constants.json
Expand All @@ -27,6 +27,7 @@ const NATIVE_KEYS = {
phone: 'hb_native_phone',
price: 'hb_native_price',
salePrice: 'hb_native_saleprice',
rendererUrl: 'hb_renderer_url',
};

// Asset type mapping as per Native IAB spec 1.2
Expand Down Expand Up @@ -160,14 +161,26 @@ export function newNativeAssetManager(win) {
/*
* Entry point to search for placeholderes and set up postmessage roundtrip
* to retrieve native assets. Looks for placeholders for the given adId and
* fires a callback after the native html is updated.
* fires a callback after the native html is updated. If no placeholders found
* 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) {
const placeholders = scanForPlaceholders(adId);
const placeholders = scanForPlaceholders(adId), flag = (typeof win.pbNativeData !== 'undefined');

if (flag && win.pbNativeData.hasOwnProperty('assetsToReplace')) {
win.pbNativeData.assetsToReplace.forEach((asset) => {
const key = (asset.match(/hb_native_/i)) ? asset : NATIVE_KEYS[asset];
if (key) {placeholders.push(key);}
});
}

if (placeholders.length > 0) {
callback = cb;
requestAssets(adId, placeholders);
}else if (flag && win.pbNativeData.hasOwnProperty('requestAllAssets') && win.pbNativeData.requestAllAssets) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space after closing brackets

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed these. Not sure how they didnt update last time around

callback = cb;
requestAllAssets(adId);
}
}

Expand All @@ -176,10 +189,11 @@ export function newNativeAssetManager(win) {
*/
function scanForPlaceholders(adId) {
let placeholders = [];
const flag = (typeof win.pbNativeData !== 'undefined');

Object.keys(NATIVE_KEYS).forEach(key => {
const placeholderKey = NATIVE_KEYS[key];
const placeholder = (adId) ? `${placeholderKey}:${adId}` : `${placeholderKey}`;
const placeholder = (adId && !flag) ? `${placeholderKey}:${adId}` : `${placeholderKey}`;
const placeholderIndex = win.document.body.innerHTML.indexOf(placeholder);

if (~placeholderIndex) {
Expand All @@ -190,6 +204,19 @@ export function newNativeAssetManager(win) {
return placeholders;
}

/*
* Searches the DOM for existence passed string
*/
function scanForString(str) {
const strIndex = win.document.body.innerHTML.indexOf(str);

if (~strIndex) {
return true;
}

return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could just do

return win.document.body.innerHTML.indexOf(str) !== -1

Not sure this function is even being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont remember this function but cant find a use for it so removing


/*
* Sends postmessage to Prebid for asset placeholders found in the native
* creative template, and setups up a listener for when Prebid responds.
Expand All @@ -204,6 +231,23 @@ export function newNativeAssetManager(win) {
assets,
};


win.parent.postMessage(JSON.stringify(message), '*');
}

/*
* Sends postmessage to Prebid for asset placeholders found in the native
* creative template, and setups up a listener for when Prebid responds.
*/
function requestAllAssets(adId) {
win.addEventListener('message', replaceAssets, false);

const message = {
message: 'Prebid Native',
action: 'allAssetRequest',
adId,
};

win.parent.postMessage(JSON.stringify(message), '*');
}

Expand All @@ -227,12 +271,45 @@ export function newNativeAssetManager(win) {
}

if (data.message === 'assetResponse') {
const body = win.document.body.innerHTML;
const newHtml = replace(body, data);
const body = win.document.body.innerHTML,flag = (typeof win.pbNativeData !== 'undefined');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split this flag out to a new declaration please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


win.document.body.innerHTML = newHtml;
callback && callback();
win.removeEventListener('message', replaceAssets);
if (flag && data.adId != win.pbNativeData.adId) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to use != vs !===?

Do we expect adId to be the same data type in both? Or is is possible one is string and one is number?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope good catch. Updated


if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (flag && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
if (win.renderAd) {
const newHtml = (win.renderAd && win.renderAd(data.assets)) || '';
win.document.body.innerHTML = body + newHtml;
callback && callback();
win.removeEventListener('message', replaceAssets);
} else if (document.getElementById('pb-native-renderer')) {
document.getElementById('pb-native-renderer').addEventListener('load', function() {
const newHtml = (win.renderAd && win.renderAd(data.assets)) || '';
win.document.body.innerHTML = body + newHtml;
callback && callback();
win.removeEventListener('message', replaceAssets);
});
} else {
loadScript(win, ((flag && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function() {
const newHtml = (win.renderAd && win.renderAd(data.assets)) || '';
win.document.body.innerHTML = body + newHtml;
callback && callback();
win.removeEventListener('message', replaceAssets);
})
}
} else if ((data.hasOwnProperty('adTemplate') && data.adTemplate)||(flag && win.pbNativeData.hasOwnProperty('adTemplate'))) {
const template = (flag && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
const newHtml = replace(template, data);

win.document.body.innerHTML = body + newHtml;
callback && callback();
win.removeEventListener('message', replaceAssets);
} else {
const newHtml = replace(body, data);

win.document.body.innerHTML = newHtml;
callback && callback();
win.removeEventListener('message', replaceAssets);
}
}
}

Expand All @@ -244,7 +321,8 @@ export function newNativeAssetManager(win) {
let html = document;

(assets || []).forEach(asset => {
const searchString = (adId) ? `${NATIVE_KEYS[asset.key]}:${adId}` : `${NATIVE_KEYS[asset.key]}`;
const flag = (typeof win.pbNativeData !== 'undefined');
const searchString = (adId && !flag) ? `${NATIVE_KEYS[asset.key]}:${adId}` : ((flag) ? '##'+`${NATIVE_KEYS[asset.key]}`+'##' : `${NATIVE_KEYS[asset.key]}`);
const searchStringRegex = new RegExp(searchString, 'g');
html = html.replace(searchStringRegex, asset.value);
});
Expand Down
7 changes: 7 additions & 0 deletions src/nativeRender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { newNativeRenderManager } from './nativeRenderManager';

window.pbNativeTag = (window.pbNativeTag || {});
const nativeRenderManager = newNativeRenderManager(window);

window.pbNativeTag.renderNativeAd = nativeRenderManager.renderNativeAd;

78 changes: 78 additions & 0 deletions src/nativeRenderManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Script to handle firing impression and click trackers from native teamplates
*/
import { parseUrl, triggerPixel, transformAuctionTargetingData } from './utils';
import { newNativeAssetManager } from './nativeAssetManager';

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

export function newNativeRenderManager(win) {
let publisherDomain;


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';
}

win.parent.postMessage(JSON.stringify(message), publisherDomain);
}
}

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(nativeTag) {
window.pbNativeData = nativeTag;
const targetingData = transformAuctionTargetingData(nativeTag);
const nativeAssetManager = newNativeAssetManager(window);

if (nativeTag.hasOwnProperty('adId')) {
let parsedUrl = parseUrl(window.pbNativeData.pubUrl);
publisherDomain = parsedUrl.protocol + '://' + parsedUrl.host;

if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
const scr = document.createElement('SCRIPT');
scr.src = nativeTag.rendererUrl,
scr.id = 'pb-native-renderer';
document.body.appendChild(scr);
}
nativeAssetManager.loadAssets(nativeTag.adId,fireNativeCallback);
fireNativeCallback();
fireNativeImpTracker(nativeTag.adId);
}else{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some more lint issues

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have a space after closing bracket befor else and after

} else {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just run enable eslint in your editor and see what else there is?

console.warn('Prebid Native Tag object was missing \'adId\'.');
}
}

return {
renderNativeAd
}
}
3 changes: 2 additions & 1 deletion src/nativeTrackers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { newNativeTrackerManager } from './nativeTrackerManager';
window.pbNativeTag = (window.pbNativeTag || {});
const nativeTrackerManager = newNativeTrackerManager(window);

window.pbNativeTag.startTrackers = nativeTrackerManager.startTrackers;
window.pbNativeTag.startTrackers = nativeTrackerManager.startTrackers;

Loading