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

add support for alternative native asset substitution #60

Closed
wants to merge 2 commits into from
Closed
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
54 changes: 47 additions & 7 deletions src/nativeAssetManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ const NATIVE_KEYS = {
salePrice: 'hb_native_saleprice',
};

const NATIVE_CLASS_IDS = {
icon: 'pb-icon',
image: 'pb-image'
};

export function newNativeAssetManager(win) {
let callback;
let errorCountEscapeHatch = 0;
let tokenType = 'default';

/*
* Entry point to search for placeholderes and set up postmessage roundtrip
Expand All @@ -51,10 +57,30 @@ export function newNativeAssetManager(win) {
function scanForPlaceholders(adId) {
let placeholders = [];

Object.keys(NATIVE_KEYS).forEach(key => {
Object.keys(NATIVE_KEYS).forEach(function(key) {
const placeholderKey = NATIVE_KEYS[key];
const placeholder = `${placeholderKey}:${adId}`;
const placeholderIndex = win.document.body.innerHTML.indexOf(placeholder);
const sendIdPlaceholder = `${placeholderKey}:${adId}`;
const hardKeyPlaceholder = `%%${placeholderKey}%%`;

let placeholderIndex = -1;
let tokensToCheck = [sendIdPlaceholder];

if (placeholderKey === NATIVE_KEYS.image) {
tokensToCheck.push('pb-image');
} else if (placeholderKey === NATIVE_KEYS.icon) {
tokensToCheck.push('pb-icon');
} else {
tokensToCheck.push(hardKeyPlaceholder);
}

tokensToCheck.forEach(function(token) {
if (win.document.body.innerHTML.indexOf(token) !== -1) {
placeholderIndex = win.document.body.innerHTML.indexOf(token);
if (tokenType === 'default') {
tokenType = (token === sendIdPlaceholder) ? 'sendId' : 'hardKey';
}
}
});

if (~placeholderIndex) {
placeholders.push(placeholderKey);
Expand Down Expand Up @@ -85,7 +111,7 @@ export function newNativeAssetManager(win) {
* Postmessage listener for when Prebid responds with requested native assets.
*/
function replaceAssets(event) {
var data = {};
let data = {};

try {
data = JSON.parse(event.data);
Expand All @@ -105,6 +131,7 @@ export function newNativeAssetManager(win) {
const newHtml = replace(body, data);

win.document.body.innerHTML = newHtml;
if (tokenType === 'hardKey') insertImages(win.document.body, data);
callback && callback();
win.removeEventListener('message', replaceAssets);
}
Expand All @@ -116,14 +143,27 @@ export function newNativeAssetManager(win) {
*/
function replace(document, { assets, adId }) {
let html = document;

(assets || []).forEach(asset => {
html = html.replace(`${NATIVE_KEYS[asset.key]}:${adId}`, asset.value);
(assets || []).forEach(function(asset) {
let tokenSyntax = (tokenType === 'sendId') ? `${NATIVE_KEYS[asset.key]}:${adId}` : `%%${NATIVE_KEYS[asset.key]}%%`;
html = html.replace(tokenSyntax, asset.value);
});

return html;
}

/**
* Adds the src attribute to specific img tags identified by the class name.
* The value for the added src is dervied from either the native.icon or native.image bid assets.
*/
function insertImages(document, { assets }) {
(assets || []).forEach(function(asset) {
if (asset.key === 'icon' || asset.key === 'image') {
let imageElement = document.getElementsByClassName(NATIVE_CLASS_IDS[asset.key]);
imageElement[0].setAttribute('src', asset.value);
}
});
}

return {
loadAssets
};
Expand Down
37 changes: 35 additions & 2 deletions test/spec/nativeAssetManager_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ function createResponder(assets) {
};
}

describe('nativeTrackerManager', () => {
describe('nativeAssetManager', () => {
let win;

beforeEach(() => {
win = merge(mocks.createFakeWindow(), mockDocument.getWindowObject());
});

it('replaces native placeholders with their asset values', () => {
it('replaces native placeholders with their asset values while using sendId notation', () => {
win.document.body.innerHTML = `
<h1>hb_native_title</h1>
<p>hb_native_body:${AD_ID}</p>
Expand All @@ -54,6 +54,39 @@ describe('nativeTrackerManager', () => {
expect(win.document.body.innerHTML).to.include('<h1>hb_native_title</h1>');
});

it('replaces native placeholders with their asset values while using hardKey notation', () => {
win.document.body.innerHTML = `
<h1>hb_native_title</h1>
<p>%%hb_native_body%%</p>
<a href="%%hb_native_linkurl%%">Click Here</a>
<img class="pb-icon" height="10" width="10" />
`;
win.document.body.getElementsByClassName = sinon.stub().returns([{
setAttribute: function(property, val) {
let attribute = `class="pb-icon" ${property}="${val}"`
win.document.body.innerHTML = win.document.body.innerHTML.replace('class="pb-icon"', attribute);
}
}]);
win.addEventListener = createResponder([
{ key: 'body', value: 'new value' },
{ key: 'clickUrl', value: 'http://www.example.com' },
{ key: 'icon', value: 'http://my.fake.images/somewhere/lost/1x1.gif' }
]);

const nativeAssetManager = newNativeAssetManager(win);
nativeAssetManager.loadAssets(AD_ID);

expect(win.document.body.innerHTML).to.include('<p>new value</p>');
expect(win.document.body.innerHTML).to.include(`
<a href="http://www.example.com">Click Here</a>
`);
expect(win.document.body.innerHTML).to.include(`
<img class="pb-icon" src="http://my.fake.images/somewhere/lost/1x1.gif" height="10" width="10" />
`)
// title was not a requested asset so this should stay as is
expect(win.document.body.innerHTML).to.include('<h1>hb_native_title</h1>');
});

it('attaches and removes message listeners', () => {
win.document.body.innerHTML = `<h1>hb_native_title:${AD_ID}</h1>`;
win.addEventListener = createResponder();
Expand Down