prebid-universal-creative 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,9 +59,22 @@ const assetTypeMapping = {
59
59
  const DEFAULT_CACHE_HOST = 'prebid.adnxs.com';
60
60
  const DEFAULT_CACHE_PATH = '/pbc/v1/cache';
61
61
 
62
- export function newNativeAssetManager(win, pubUrl) {
63
- const sendMessage = prebidMessenger(pubUrl, win);
64
- let callback;
62
+ const CLICK_URL_UNESC = `%%CLICK_URL_UNESC%%`;
63
+
64
+ let clickUrlUnesc = '';
65
+
66
+ export function newNativeAssetManager(win, nativeTag, mkMessenger = prebidMessenger) {
67
+
68
+ // clickUrlUnesc contains the url to track clicks in GAM. we check if it
69
+ // has been transformed, by GAM, in an URL.
70
+ // if CLICK_URL_UNESC is the string "%%CLICK_URL_UNESC%%", we're not in GAM.
71
+ if (nativeTag.clickUrlUnesc && nativeTag.clickUrlUnesc !== CLICK_URL_UNESC) {
72
+ clickUrlUnesc = nativeTag.clickUrlUnesc;
73
+ }
74
+ const {pubUrl} = nativeTag;
75
+
76
+ const sendMessage = mkMessenger(pubUrl, win);
77
+ let callback, errCallback;
65
78
  let errorCountEscapeHatch = 0;
66
79
  let cancelMessageListener;
67
80
 
@@ -183,7 +196,8 @@ export function newNativeAssetManager(win, pubUrl) {
183
196
  * and requestAllAssets flag is set in the tag, postmessage roundtrip
184
197
  * to retrieve native assets that have a value on the corresponding bid
185
198
  */
186
- function loadAssets(adId, cb) {
199
+ function loadAssets(adId, cb, onError) {
200
+ errCallback = onError;
187
201
  const placeholders = scanDOMForPlaceHolders(adId);
188
202
 
189
203
  if (hasPbNativeData() && win.pbNativeData.hasOwnProperty('assetsToReplace')) {
@@ -199,6 +213,8 @@ export function newNativeAssetManager(win, pubUrl) {
199
213
  } else if (placeholders.length > 0) {
200
214
  callback = cb;
201
215
  cancelMessageListener = requestAssets(adId, placeholders);
216
+ } else {
217
+ onError && onError(new Error('No assets to load: no placeholders found in template'));
202
218
  }
203
219
  }
204
220
 
@@ -273,69 +289,83 @@ export function newNativeAssetManager(win, pubUrl) {
273
289
  * Postmessage listener for when Prebid responds with requested native assets.
274
290
  */
275
291
  function replaceAssets(event) {
276
- var data = {};
277
-
278
292
  try {
279
- data = JSON.parse(event.data);
280
- } catch (e) {
281
- if (errorCountEscapeHatch++ > 10) {
282
- /*
283
- * if for some reason Prebid never responds with the native assets,
284
- * get rid of this listener because other messages won't stop coming
285
- */
286
- stopListening();
293
+
294
+ var data = {};
295
+
296
+ try {
297
+ data = JSON.parse(event.data);
298
+ } catch (e) {
299
+ if (errorCountEscapeHatch++ > 10) {
300
+ // TODO: this should be a timeout, not an arbitrary cap on the number of messages received
301
+ /*
302
+ * if for some reason Prebid never responds with the native assets,
303
+ * get rid of this listener because other messages won't stop coming
304
+ */
305
+ stopListening();
306
+ throw e;
307
+ }
308
+ return;
287
309
  }
288
- return;
289
- }
290
310
 
291
311
  if (data.message === 'assetResponse') {
312
+ // add GAM %%CLICK_URL_UNESC%% to the data object to be eventually used in renderers
313
+ data.clickUrlUnesc = clickUrlUnesc;
292
314
  const body = win.document.body.innerHTML;
293
315
  const head = win.document.head.innerHTML;
294
316
 
295
- if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;
317
+ if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;
296
318
 
297
- if (head) win.document.head.innerHTML = replace(head, data);
319
+ callback = ((cb) => {
320
+ return () => {
321
+ fireNativeImpressionTrackers(data.adId, sendMessage);
322
+ addNativeClickTrackers(data.adId, sendMessage);
323
+ cb && cb();
324
+ }
325
+ })(callback);
298
326
 
299
- data.assets = data.assets || [];
300
- let renderPayload = data.assets;
301
- if (data.ortb) {
302
- renderPayload.ortb = data.ortb;
303
- callback = () => {
304
- fireNativeImpressionTrackers(data.adId, sendMessage);
305
- addNativeClickTrackers(data.adId, data.ortb, sendMessage);
306
- }
307
- }
327
+ if (head) win.document.head.innerHTML = replace(head, data);
308
328
 
309
- if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
310
- if (win.renderAd) {
311
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
312
329
 
313
- renderAd(newHtml, data);
314
- } else if (document.getElementById('pb-native-renderer')) {
315
- document.getElementById('pb-native-renderer').addEventListener('load', function() {
330
+ data.assets = data.assets || [];
331
+ let renderPayload = data.assets;
332
+ if (data.ortb) {
333
+ renderPayload.ortb = data.ortb;
334
+ }
335
+
336
+ if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
337
+ if (win.renderAd) {
316
338
  const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
317
339
 
318
340
  renderAd(newHtml, data);
319
- });
341
+ } else if (document.getElementById('pb-native-renderer')) {
342
+ document.getElementById('pb-native-renderer').addEventListener('load', function () {
343
+ const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
344
+
345
+ renderAd(newHtml, data);
346
+ });
347
+ } else {
348
+ loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function () {
349
+ const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
350
+
351
+ renderAd(newHtml, data);
352
+ });
353
+ }
354
+ } else if ((data.hasOwnProperty('adTemplate') && data.adTemplate) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
355
+ const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
356
+ const newHtml = replace(template, data);
357
+
358
+ renderAd(newHtml, data);
320
359
  } else {
321
- loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function() {
322
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
360
+ const newHtml = replace(body, data);
323
361
 
324
- renderAd(newHtml, data);
325
- })
362
+ win.document.body.innerHTML = newHtml;
363
+ callback && callback();
364
+ stopListening();
326
365
  }
327
- } else if ((data.hasOwnProperty('adTemplate') && data.adTemplate)||(hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
328
- const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
329
- const newHtml = replace(template, data);
330
-
331
- renderAd(newHtml, data);
332
- } else {
333
- const newHtml = replace(body, data);
334
-
335
- win.document.body.innerHTML = newHtml;
336
- callback && callback();
337
- stopListening();
338
366
  }
367
+ } catch (e) {
368
+ errCallback && errCallback(e);
339
369
  }
340
370
  }
341
371
 
@@ -349,7 +379,7 @@ export function newNativeAssetManager(win, pubUrl) {
349
379
  if (!currentParentWindow.frames || !currentParentWindow.frames.length) return null;
350
380
  for (let idx = 0; idx < currentParentWindow.frames.length; idx++)
351
381
  if (currentParentWindow.frames[idx] === currentWindow) {
352
- if (!currentParentWindow.document) return null;
382
+ if (!currentParentWindow.document) return null;
353
383
  for (let frameElement of currentParentWindow.document.getElementsByTagName('iframe')) {
354
384
  if (!frameElement.contentWindow) return null;
355
385
  if (frameElement.contentWindow === currentWindow) {
@@ -368,18 +398,23 @@ export function newNativeAssetManager(win, pubUrl) {
368
398
  if (!isSafeFrame(window)) {
369
399
  let iframeContainer = getCurrentFrameContainer(win);
370
400
  if (iframeContainer && iframeContainer.children && iframeContainer.children[0]) {
371
- const iframe = iframeContainer.children[0];
401
+ const iframe = iframeContainer.children[0];
372
402
  if (iframe.width === '1' && iframe.height === '1') {
373
403
  let width = iframeContainer.getBoundingClientRect().width;
374
404
  win.document.body.style.width = `${width}px`;
375
405
  }
376
406
  }
377
407
  }
408
+
409
+ //substitute CLICK_URL_UNESC with actual value
410
+ html = html.replaceAll(CLICK_URL_UNESC, bid.clickUrlUnesc || "");
411
+
378
412
  win.document.body.innerHTML += html;
379
413
  callback && callback();
380
414
  win.removeEventListener('message', replaceAssets);
381
415
  stopListening();
382
- requestHeightResize(bid.adId, (document.body.clientHeight || document.body.offsetHeight), document.body.clientWidth);
416
+ const resize = () => requestHeightResize(bid.adId, (document.body.clientHeight || document.body.offsetHeight), document.body.clientWidth);
417
+ document.readyState === 'complete' ? resize() : window.onload = resize;
383
418
 
384
419
  if (typeof window.postRenderAd === 'function') {
385
420
  window.postRenderAd(bid);
@@ -405,22 +440,22 @@ export function newNativeAssetManager(win, pubUrl) {
405
440
  }
406
441
 
407
442
  ortb.assets.forEach(asset => {
408
- html = html.replace(`##hb_native_asset_id_${asset.id}##`, getAssetValue(asset));
443
+ html = html.replaceAll(`##hb_native_asset_id_${asset.id}##`, getAssetValue(asset));
409
444
  if (asset.link && asset.link.url) {
410
- html = html.replace(`##hb_native_asset_link_id_${asset.id}##`, asset.link.url);
445
+ html = html.replaceAll(`##hb_native_asset_link_id_${asset.id}##`, asset.link.url);
411
446
  }
412
447
  });
413
448
 
414
449
  html = html.replaceAll(/##hb_native_asset_id_\d+##/gm, '');
415
450
 
416
451
  if (ortb.privacy) {
417
- html = html.replace("##hb_native_privacy##", ortb.privacy);
452
+ html = html.replaceAll("##hb_native_privacy##", ortb.privacy);
418
453
  }
419
454
 
420
455
  if (ortb.link) {
421
456
  html = html.replaceAll("##hb_native_linkurl##", ortb.link.url);
422
457
  }
423
-
458
+
424
459
  return html;
425
460
  }
426
461
 
@@ -12,11 +12,11 @@ export function fireNativeImpressionTrackers(adId, sendMessage) {
12
12
  sendMessage(message);
13
13
  }
14
14
 
15
- export function addNativeClickTrackers(adId, ortb, sendMessage) {
15
+ export function addNativeClickTrackers(adId, sendMessage) {
16
16
  const message = {
17
17
  message: 'Prebid Native',
18
18
  action: 'click',
19
- adId
19
+ adId
20
20
  };
21
21
  const adElements = document.getElementsByClassName(AD_ANCHOR_CLASS_NAME) || [];
22
22
  // get all assets that have 'link' property, map asset.id -> asset.link
@@ -1,76 +1,53 @@
1
1
  /*
2
2
  * Script to handle firing impression and click trackers from native teamplates
3
3
  */
4
- import { newNativeAssetManager } from './nativeAssetManager';
4
+ import {newNativeAssetManager} from './nativeAssetManager';
5
5
  import {prebidMessenger} from './messaging.js';
6
6
 
7
- const AD_ANCHOR_CLASS_NAME = 'pb-click';
8
- const AD_DATA_ADID_ATTRIBUTE = 'pbAdId';
9
-
10
- export function newNativeRenderManager(win) {
11
- let sendMessage;
12
-
13
-
14
- function findAdElements(className) {
15
- let adElements = win.document.getElementsByClassName(className);
16
- return adElements || [];
17
- }
18
-
19
- function loadClickTrackers(event, adId) {
20
- fireTracker(adId, 'click');
21
- }
22
-
23
- function fireTracker(adId, action) {
24
- if (adId === '') {
25
- 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');
26
- } else {
27
- let message = { message: 'Prebid Native', adId: adId };
28
-
29
- // fires click trackers when called via link
30
- if (action === 'click') {
31
- message.action = 'click';
32
- }
33
- sendMessage(message);
34
- }
35
- }
36
-
37
- function fireNativeImpTracker(adId) {
38
- fireTracker(adId, 'impression');
39
- }
40
-
41
- function fireNativeCallback() {
42
- const adElements = findAdElements(AD_ANCHOR_CLASS_NAME);
43
- for (let i = 0; i < adElements.length; i++) {
44
- adElements[i].addEventListener('click', function(event) {
45
- loadClickTrackers(event, window.pbNativeData.adId);
46
- }, true);
47
- }
48
- }
49
-
50
- // START OF MAIN CODE
51
- let renderNativeAd = function(doc, nativeTag) {
52
- window.pbNativeData = nativeTag;
53
- sendMessage = prebidMessenger(nativeTag.pubUrl, win);
54
- const nativeAssetManager = newNativeAssetManager(window, nativeTag.pubUrl);
55
-
56
- if (nativeTag.hasOwnProperty('adId')) {
57
-
58
- if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
59
- const scr = doc.createElement('SCRIPT');
60
- scr.src = nativeTag.rendererUrl,
61
- scr.id = 'pb-native-renderer';
62
- doc.body.appendChild(scr);
63
- }
64
- nativeAssetManager.loadAssets(nativeTag.adId, () => {
65
- fireNativeImpTracker(nativeTag.adId);
66
- fireNativeCallback();
67
- });
68
- } else {
69
- console.warn("Prebid Native Tag object was missing 'adId'.");
70
- }
71
- }
72
-
73
- return {
74
- renderNativeAd
75
- }
7
+ export function newNativeRenderManager(win, mkMessenger = prebidMessenger, assetMgr = newNativeAssetManager) {
8
+ let sendMessage;
9
+
10
+
11
+ let renderNativeAd = function (doc, nativeTag) {
12
+ window.pbNativeData = nativeTag;
13
+ sendMessage = mkMessenger(nativeTag.pubUrl, win);
14
+
15
+ function signalResult(adId, success, info) {
16
+ sendMessage({
17
+ message: 'Prebid Event',
18
+ adId,
19
+ event: success ? 'adRenderSucceeded' : 'adRenderFailed',
20
+ info
21
+ });
22
+ }
23
+
24
+ try {
25
+ const nativeAssetManager = assetMgr(window, nativeTag);
26
+
27
+ if (nativeTag.adId != null) {
28
+
29
+ if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
30
+ const scr = doc.createElement('SCRIPT');
31
+ scr.src = nativeTag.rendererUrl,
32
+ scr.id = 'pb-native-renderer';
33
+ doc.body.appendChild(scr);
34
+ }
35
+ nativeAssetManager.loadAssets(nativeTag.adId, () => {
36
+ signalResult(nativeTag.adId, true);
37
+ }, (e) => {
38
+ signalResult(nativeTag.adId, false, {reason: 'exception', message: e.message});
39
+ });
40
+ } else {
41
+ signalResult(null, false, {reason: 'missingDocOrAdid'});
42
+ console.warn('Prebid Native Tag object was missing \'adId\'.');
43
+ }
44
+ } catch (e) {
45
+ signalResult(nativeTag && nativeTag.adId, false, {reason: 'exception', message: e.message});
46
+ console.error('Error rendering ad', e);
47
+ }
48
+ };
49
+
50
+ return {
51
+ renderNativeAd
52
+ };
76
53
  }
@@ -1,8 +1,10 @@
1
1
  import postscribe from 'postscribe';
2
2
 
3
- export function writeAdHtml(markup) {
4
- postscribe(document.body, markup, {
5
- error: console.error
3
+ export function writeAdHtml(markup, ps = postscribe) {
4
+ // remove <?xml> and <!doctype> tags
5
+ // https://github.com/prebid/prebid-universal-creative/issues/134
6
+ markup = markup.replace(/\<(\?xml|(\!DOCTYPE[^\>\[]+(\[[^\]]+)?))+[^>]+\>/g, '');
7
+ ps(document.body, markup, {
8
+ error: console.error
6
9
  });
7
- }
8
-
10
+ }
@@ -19,11 +19,13 @@ export function renderBannerOrDisplayAd(doc, dataObject) {
19
19
  * @param {string} adId Id of creative to render
20
20
  */
21
21
  export function renderLegacy(doc, adId) {
22
+ let found = false;
22
23
  let w = window;
23
24
  for (let i = 0; i < 10; i++) {
24
25
  w = w.parent;
25
26
  if (w.$$PREBID_GLOBAL$$) {
26
27
  try {
28
+ found = true;
27
29
  w.$$PREBID_GLOBAL$$.renderAd(doc, adId);
28
30
  break;
29
31
  } catch (e) {
@@ -31,6 +33,9 @@ export function renderLegacy(doc, adId) {
31
33
  }
32
34
  }
33
35
  }
36
+ if (!found) {
37
+ console.error("Unable to locate $$PREBID_GLOBAL$$.renderAd function!");
38
+ }
34
39
  }
35
40
 
36
41
  /**
package/src/utils.js CHANGED
@@ -30,15 +30,6 @@ export function writeAdUrl(adUrl, width, height) {
30
30
  document.body.appendChild(iframe);
31
31
  }
32
32
 
33
- export function writeAdHtml(markup) {
34
- // remove <?xml> and <!doctype> tags
35
- // https://github.com/prebid/prebid-universal-creative/issues/134
36
- markup = markup.replace(/\<(\?xml|(\!DOCTYPE[^\>\[]+(\[[^\]]+)?))+[^>]+\>/g, '');
37
- postscribe(document.body, markup, {
38
- error: console.error
39
- });
40
- }
41
-
42
33
  export function sendRequest(url, callback) {
43
34
  function reqListener() {
44
35
  callback(oReq.responseText);
@@ -4,6 +4,7 @@ import * as utils from 'src/utils';
4
4
  import { expect } from 'chai';
5
5
  import { mocks } from 'test/helpers/mocks';
6
6
  import { merge } from 'lodash';
7
+ import {writeAdHtml} from 'src/postscribeRender';
7
8
 
8
9
 
9
10
  function renderingMocks() {
@@ -306,3 +307,10 @@ describe("renderingManager", function () {
306
307
  });
307
308
  });
308
309
 
310
+ describe('writeAdHtml', () => {
311
+ it('removes DOCTYPE from markup', () => {
312
+ const ps = sinon.stub();
313
+ writeAdHtml('<!DOCTYPE html><div>mock-ad</div>', ps);
314
+ sinon.assert.calledWith(ps, sinon.match.any, '<div>mock-ad</div>')
315
+ })
316
+ })