prebid-universal-creative 1.14.2 → 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.
Files changed (41) hide show
  1. package/.babelrc +1 -2
  2. package/.github/workflows/issue_tracker.yml +31 -16
  3. package/README.md +21 -3
  4. package/dist/amp.js +3 -0
  5. package/dist/banner.js +3 -0
  6. package/dist/creative.js +4 -3
  7. package/dist/creative.max.js +546 -593
  8. package/dist/load-cookie-with-consent.html +1 -1
  9. package/dist/load-cookie.html +1 -1
  10. package/dist/mobile.js +3 -0
  11. package/dist/native-render.js +3 -3
  12. package/dist/native-trk.js +3 -3
  13. package/dist/native.js +3 -0
  14. package/dist/uid.js +3 -3
  15. package/dist/video.js +3 -0
  16. package/gulpfile.js +84 -45
  17. package/karma.conf.maker.js +4 -6
  18. package/package.json +80 -82
  19. package/src/ampOrMobile.js +14 -0
  20. package/src/creative.js +2 -9
  21. package/src/environment.js +62 -75
  22. package/src/legacy.js +29 -0
  23. package/src/legacyNativeRender.js +6 -0
  24. package/src/mobileAndAmpRender.js +239 -0
  25. package/src/nativeAssetManager.js +91 -57
  26. package/src/nativeORTBTrackerManager.js +2 -2
  27. package/src/nativeRender.js +2 -2
  28. package/src/nativeRenderManager.js +46 -69
  29. package/src/postscribeRender.js +10 -0
  30. package/src/renderingManager.js +106 -358
  31. package/src/utils.js +1 -11
  32. package/template/amp/dfp-creative.html +1 -1
  33. package/test/spec/environment_spec.js +4 -11
  34. package/test/spec/legacyNativeRender_spec.js +25 -0
  35. package/test/spec/mobileAndAmpRender_spec.js +316 -0
  36. package/test/spec/nativeAssetManager_spec.js +227 -79
  37. package/test/spec/nativeORTBTrackerManager_spec.js +3 -19
  38. package/test/spec/nativeRenderManager_spec.js +77 -55
  39. package/test/spec/nativeRender_spec.js +23 -0
  40. package/test/spec/renderingManager_spec.js +16 -265
  41. package/webpack.conf.js +3 -1
@@ -0,0 +1,239 @@
1
+ import { getCreativeCommentMarkup, triggerPixel, createTrackPixelHtml, loadScript, getCreativeComment, writeAdUrl, transformAuctionTargetingData, sendRequest, getUUID } from './utils';
2
+ import { isSafeFrame, isMobileApp } from './environment';
3
+ import { insertElement } from './domHelper';
4
+ import { writeAdHtml } from './postscribeRender';
5
+
6
+ const DEFAULT_CACHE_HOST = 'prebid.adnxs.com';
7
+ const DEFAULT_CACHE_PATH = '/pbc/v1/cache';
8
+
9
+ /**
10
+ * Render mobile or amp ad
11
+ * @param {string} cacheHost Cache host
12
+ * @param {string} cachePath Cache path
13
+ * @param {string} uuid id to render response from cache endpoint
14
+ * @param {string} size size of the creative
15
+ * @param {string} hbPb final price of the winning bid
16
+ * @param {Bool} isMobileApp flag to detect mobile app
17
+ */
18
+ export function renderAmpOrMobileAd(dataObject) {
19
+ const targetingData = transformAuctionTargetingData(dataObject);
20
+ let { cacheHost, cachePath, uuid, size, hbPb } = targetingData;
21
+ uuid = uuid || '';
22
+ // For MoPub, creative is stored in localStorage via SDK.
23
+ let search = 'Prebid_';
24
+ if (uuid.substr(0, search.length) === search) {
25
+ loadFromLocalCache(uuid);
26
+ //register creative right away to not miss initial geom-update
27
+ updateIframe(size);
28
+ } else {
29
+ let adUrl = `${getCacheEndpoint(cacheHost, cachePath)}?uuid=${uuid}`;
30
+ //register creative right away to not miss initial geom-update
31
+ updateIframe(size);
32
+ sendRequest(adUrl, responseCallback(isMobileApp(targetingData.env), hbPb));
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Load response from localStorage. In case of MoPub, sdk caches response
38
+ * @param {string} cacheId
39
+ */
40
+ function loadFromLocalCache(cacheId) {
41
+ let bid = window.localStorage.getItem(cacheId);
42
+ let displayFn = responseCallback(true);
43
+ displayFn(bid);
44
+ }
45
+
46
+ /**
47
+ * update iframe by using size string to resize
48
+ * @param {string} size
49
+ */
50
+ function updateIframe(size) {
51
+ if (size) {
52
+ const sizeArr = size.split('x').map(Number);
53
+ resizeIframe(sizeArr[0], sizeArr[1]);
54
+ } else {
55
+ console.log('Targeting key hb_size not found to resize creative');
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Resize container iframe
61
+ * @param {Number} width width of creative
62
+ * @param {Number} height height of creative
63
+ */
64
+ function resizeIframe(width, height) {
65
+ if (isSafeFrame(window)) {
66
+ const iframeWidth = window.innerWidth;
67
+ const iframeHeight = window.innerHeight;
68
+
69
+ function resize(status) {
70
+ let newWidth = width - iframeWidth;
71
+ let newHeight = height - iframeHeight;
72
+ window.$sf.ext.expand({ r: newWidth, b: newHeight, push: true });
73
+ }
74
+
75
+ if (iframeWidth !== width || iframeHeight !== height) {
76
+ window.$sf.ext.register(width, height, resize);
77
+ // we need to resize the DFP container as well
78
+ window.parent.postMessage({
79
+ sentinel: 'amp',
80
+ type: 'embed-size',
81
+ width: width,
82
+ height: height
83
+ }, '*');
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Returns cache endpoint concatenated with cache path
90
+ * @param {string} cacheHost Cache Endpoint host
91
+ * @param {string} cachePath Cache Endpoint path
92
+ */
93
+ function getCacheEndpoint(cacheHost, cachePath) {
94
+ let host = (typeof cacheHost === 'undefined' || cacheHost === "") ? DEFAULT_CACHE_HOST : cacheHost;
95
+ let path = (typeof cachePath === 'undefined' || cachePath === "") ? DEFAULT_CACHE_PATH : cachePath;
96
+
97
+ return `https://${host}${path}`;
98
+ }
99
+
100
+
101
+ /**
102
+ * Cache request Callback to display creative
103
+ * @param {Bool} isMobileApp
104
+ * @param {string} hbPb final price of the winning bid
105
+ * @returns {function} a callback function that parses response
106
+ */
107
+ function responseCallback(isMobileApp, hbPb) {
108
+ return function (response) {
109
+ let bidObject = parseResponse(response);
110
+ let auctionPrice = bidObject.price || hbPb;
111
+ let ad = getCreativeCommentMarkup(bidObject);
112
+ let width = (bidObject.width) ? bidObject.width : bidObject.w;
113
+ let height = (bidObject.height) ? bidObject.height : bidObject.h;
114
+
115
+ // When Prebid Universal Creative reads from Prebid Cache, we need to have it check for the existence of the wurl parameter. If it exists, hit it.
116
+ if (bidObject.wurl) {
117
+ triggerPixel(decodeURIComponent(bidObject.wurl));
118
+ }
119
+
120
+ if (bidObject.adm) {
121
+ if (auctionPrice) { // replace ${AUCTION_PRICE} macro with the bidObject.price or hb_pb.
122
+ bidObject.adm = bidObject.adm.replace('${AUCTION_PRICE}', auctionPrice);
123
+ } else {
124
+ /*
125
+ From OpenRTB spec 2.5: If the source value is an optional parameter that was not specified, the macro will simply be removed (i.e., replaced with a zero-length string).
126
+ */
127
+ bidObject.adm = bidObject.adm.replace('${AUCTION_PRICE}', '');
128
+ }
129
+ ad += (isMobileApp) ? constructMarkup(bidObject.adm, width, height) : bidObject.adm;
130
+ if (bidObject.nurl) {
131
+ ad += createTrackPixelHtml(decodeURIComponent(bidObject.nurl));
132
+ }
133
+ if (bidObject.burl) {
134
+ let triggerBurl = function () { triggerPixel(bidObject.burl); };
135
+ if (isMobileApp) {
136
+ let mraidScript = loadScript(window, 'mraid.js',
137
+ function () { // Success loading MRAID
138
+ let result = registerMRAIDViewableEvent(triggerBurl);
139
+ if (!result) {
140
+ triggerBurl(); // Error registering event
141
+ }
142
+ },
143
+ triggerBurl // Error loading MRAID
144
+ );
145
+ } else {
146
+ triggerBurl(); // Not a mobile app
147
+ }
148
+ }
149
+ writeAdHtml(ad);
150
+ } else if (bidObject.nurl) {
151
+ if (isMobileApp) {
152
+ let adhtml = loadScript(window, bidObject.nurl);
153
+ ad += constructMarkup(adhtml.outerHTML, width, height);
154
+
155
+ writeAdHtml(ad);
156
+ } else {
157
+ let nurl = bidObject.nurl;
158
+ let commentElm = getCreativeComment(bidObject);
159
+ insertElement(commentElm, document, 'body');
160
+ writeAdUrl(nurl, width, height);
161
+ }
162
+ }
163
+ }
164
+ };
165
+
166
+ /**
167
+ * Parse response
168
+ * @param {string} response
169
+ * @returns {Object} bidObject parsed response
170
+ */
171
+ function parseResponse(response) {
172
+ let bidObject;
173
+ try {
174
+ bidObject = JSON.parse(response);
175
+ } catch (error) {
176
+ console.log(`Error parsing response from cache host: ${error}`);
177
+ }
178
+ return bidObject;
179
+ }
180
+
181
+ /**
182
+ * Wrap mobile app creative in div
183
+ * @param {string} ad html for creative
184
+ * @param {Number} width width of creative
185
+ * @param {Number} height height of creative
186
+ * @returns {string} creative markup
187
+ */
188
+ function constructMarkup(ad, width, height) {
189
+ let id = getUUID();
190
+ return `<div id="${id}" style="border-style: none; position: absolute; width:100%; height:100%;">
191
+ <div id="${id}_inner" style="margin: 0 auto; width:${width}px; height:${height}px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">${ad}</div>
192
+ </div>`;
193
+ }
194
+
195
+
196
+ function registerMRAIDViewableEvent(callback) {
197
+
198
+ function exposureChangeListener(exposure) {
199
+ if (exposure > 0) {
200
+ mraid.removeEventListener('exposureChange', exposureChangeListener);
201
+ callback();
202
+ }
203
+ }
204
+
205
+ function viewableChangeListener(viewable) {
206
+ if (viewable) {
207
+ mraid.removeEventListener('viewableChange', viewableChangeListener);
208
+ callback();
209
+ }
210
+ }
211
+
212
+ function registerViewableChecks() {
213
+ if (window.MRAID_ENV && parseFloat(window.MRAID_ENV.version) >= 3) {
214
+ mraid.addEventListener('exposureChange', exposureChangeListener);
215
+ } else if (window.MRAID_ENV && parseFloat(window.MRAID_ENV.version) < 3) {
216
+ if (mraid.isViewable()) {
217
+ callback();
218
+ } else {
219
+ mraid.addEventListener('viewableChange', viewableChangeListener);
220
+ }
221
+ }
222
+ }
223
+
224
+ function readyListener() {
225
+ mraid.removeEventListener('ready', readyListener);
226
+ registerViewableChecks();
227
+ }
228
+
229
+ if (window.mraid && window.MRAID_ENV) {
230
+ if (mraid.getState() == 'loading') {
231
+ mraid.addEventListener('ready', readyListener);
232
+ } else {
233
+ registerViewableChecks();
234
+ }
235
+ return true;
236
+ } else {
237
+ return false;
238
+ }
239
+ }
@@ -6,9 +6,7 @@
6
6
  import { addNativeClickTrackers, fireNativeImpressionTrackers } from './nativeORTBTrackerManager';
7
7
  import { sendRequest, loadScript } from './utils';
8
8
  import {prebidMessenger} from './messaging.js';
9
- import { newEnvironment } from './environment.js';
10
-
11
- const envionment = newEnvironment(window);
9
+ import { isSafeFrame } from './environment.js';
12
10
  /*
13
11
  * Native asset->key mapping from Prebid.js/src/constants.json
14
12
  * https://github.com/prebid/Prebid.js/blob/8635c91942de9df4ec236672c39b19448545a812/src/constants.json#L67
@@ -18,6 +16,7 @@ const NATIVE_KEYS = {
18
16
  body: 'hb_native_body',
19
17
  body2: 'hb_native_body2',
20
18
  privacyLink: 'hb_native_privacy',
19
+ privacyIcon: 'hb_native_privicon',
21
20
  sponsoredBy: 'hb_native_brand',
22
21
  image: 'hb_native_image',
23
22
  icon: 'hb_native_icon',
@@ -60,9 +59,22 @@ const assetTypeMapping = {
60
59
  const DEFAULT_CACHE_HOST = 'prebid.adnxs.com';
61
60
  const DEFAULT_CACHE_PATH = '/pbc/v1/cache';
62
61
 
63
- export function newNativeAssetManager(win, pubUrl) {
64
- const sendMessage = prebidMessenger(pubUrl, win);
65
- 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;
66
78
  let errorCountEscapeHatch = 0;
67
79
  let cancelMessageListener;
68
80
 
@@ -184,7 +196,8 @@ export function newNativeAssetManager(win, pubUrl) {
184
196
  * and requestAllAssets flag is set in the tag, postmessage roundtrip
185
197
  * to retrieve native assets that have a value on the corresponding bid
186
198
  */
187
- function loadAssets(adId, cb) {
199
+ function loadAssets(adId, cb, onError) {
200
+ errCallback = onError;
188
201
  const placeholders = scanDOMForPlaceHolders(adId);
189
202
 
190
203
  if (hasPbNativeData() && win.pbNativeData.hasOwnProperty('assetsToReplace')) {
@@ -200,6 +213,8 @@ export function newNativeAssetManager(win, pubUrl) {
200
213
  } else if (placeholders.length > 0) {
201
214
  callback = cb;
202
215
  cancelMessageListener = requestAssets(adId, placeholders);
216
+ } else {
217
+ onError && onError(new Error('No assets to load: no placeholders found in template'));
203
218
  }
204
219
  }
205
220
 
@@ -274,69 +289,83 @@ export function newNativeAssetManager(win, pubUrl) {
274
289
  * Postmessage listener for when Prebid responds with requested native assets.
275
290
  */
276
291
  function replaceAssets(event) {
277
- var data = {};
278
-
279
292
  try {
280
- data = JSON.parse(event.data);
281
- } catch (e) {
282
- if (errorCountEscapeHatch++ > 10) {
283
- /*
284
- * if for some reason Prebid never responds with the native assets,
285
- * get rid of this listener because other messages won't stop coming
286
- */
287
- 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;
288
309
  }
289
- return;
290
- }
291
310
 
292
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;
293
314
  const body = win.document.body.innerHTML;
294
315
  const head = win.document.head.innerHTML;
295
316
 
296
- if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;
317
+ if (hasPbNativeData() && data.adId !== win.pbNativeData.adId) return;
297
318
 
298
- 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);
299
326
 
300
- data.assets = data.assets || [];
301
- let renderPayload = data.assets;
302
- if (data.ortb) {
303
- renderPayload.ortb = data.ortb;
304
- callback = () => {
305
- fireNativeImpressionTrackers(data.adId, sendMessage);
306
- addNativeClickTrackers(data.adId, data.ortb, sendMessage);
307
- }
308
- }
327
+ if (head) win.document.head.innerHTML = replace(head, data);
309
328
 
310
- if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
311
- if (win.renderAd) {
312
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
313
329
 
314
- renderAd(newHtml, data);
315
- } else if (document.getElementById('pb-native-renderer')) {
316
- document.getElementById('pb-native-renderer').addEventListener('load', function() {
317
- const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
330
+ data.assets = data.assets || [];
331
+ let renderPayload = data.assets;
332
+ if (data.ortb) {
333
+ renderPayload.ortb = data.ortb;
334
+ }
318
335
 
319
- renderAd(newHtml, data);
320
- });
321
- } else {
322
- loadScript(win, ((hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl') && win.pbNativeData.rendererUrl) || data.rendererUrl), function() {
336
+ if ((data.hasOwnProperty('rendererUrl') && data.rendererUrl) || (hasPbNativeData() && win.pbNativeData.hasOwnProperty('rendererUrl'))) {
337
+ if (win.renderAd) {
323
338
  const newHtml = (win.renderAd && win.renderAd(renderPayload)) || '';
324
339
 
325
340
  renderAd(newHtml, data);
326
- })
327
- }
328
- } else if ((data.hasOwnProperty('adTemplate') && data.adTemplate)||(hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
329
- const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
330
- const newHtml = replace(template, data);
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);
331
357
 
332
- renderAd(newHtml, data);
333
- } else {
334
- const newHtml = replace(body, data);
358
+ renderAd(newHtml, data);
359
+ } else {
360
+ const newHtml = replace(body, data);
335
361
 
336
- win.document.body.innerHTML = newHtml;
337
- callback && callback();
338
- stopListening();
362
+ win.document.body.innerHTML = newHtml;
363
+ callback && callback();
364
+ stopListening();
365
+ }
339
366
  }
367
+ } catch (e) {
368
+ errCallback && errCallback(e);
340
369
  }
341
370
  }
342
371
 
@@ -366,7 +395,7 @@ export function newNativeAssetManager(win, pubUrl) {
366
395
  // current iframe width to the width of the container. This
367
396
  // is to handle the case where the native ad is rendered inside
368
397
  // a GAM display ad.
369
- if (!envionment.isSafeFrame()) {
398
+ if (!isSafeFrame(window)) {
370
399
  let iframeContainer = getCurrentFrameContainer(win);
371
400
  if (iframeContainer && iframeContainer.children && iframeContainer.children[0]) {
372
401
  const iframe = iframeContainer.children[0];
@@ -376,11 +405,16 @@ export function newNativeAssetManager(win, pubUrl) {
376
405
  }
377
406
  }
378
407
  }
408
+
409
+ //substitute CLICK_URL_UNESC with actual value
410
+ html = html.replaceAll(CLICK_URL_UNESC, bid.clickUrlUnesc || "");
411
+
379
412
  win.document.body.innerHTML += html;
380
413
  callback && callback();
381
414
  win.removeEventListener('message', replaceAssets);
382
415
  stopListening();
383
- 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;
384
418
 
385
419
  if (typeof window.postRenderAd === 'function') {
386
420
  window.postRenderAd(bid);
@@ -406,16 +440,16 @@ export function newNativeAssetManager(win, pubUrl) {
406
440
  }
407
441
 
408
442
  ortb.assets.forEach(asset => {
409
- html = html.replace(`##hb_native_asset_id_${asset.id}##`, getAssetValue(asset));
443
+ html = html.replaceAll(`##hb_native_asset_id_${asset.id}##`, getAssetValue(asset));
410
444
  if (asset.link && asset.link.url) {
411
- 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);
412
446
  }
413
447
  });
414
448
 
415
449
  html = html.replaceAll(/##hb_native_asset_id_\d+##/gm, '');
416
450
 
417
451
  if (ortb.privacy) {
418
- html = html.replace("##hb_native_privacy##", ortb.privacy);
452
+ html = html.replaceAll("##hb_native_privacy##", ortb.privacy);
419
453
  }
420
454
 
421
455
  if (ortb.link) {
@@ -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,7 +1,7 @@
1
1
  import { newNativeRenderManager } from './nativeRenderManager';
2
2
 
3
- window.pbNativeTag = (window.pbNativeTag || {});
4
3
  const nativeRenderManager = newNativeRenderManager(window);
5
4
 
6
- window.pbNativeTag.renderNativeAd = nativeRenderManager.renderNativeAd;
5
+ window.ucTag = (window.ucTag || {});
7
6
 
7
+ window.ucTag.renderAd = nativeRenderManager.renderNativeAd;
@@ -4,73 +4,50 @@
4
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(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 = document.createElement('SCRIPT');
60
- scr.src = nativeTag.rendererUrl,
61
- scr.id = 'pb-native-renderer';
62
- document.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
  }
@@ -0,0 +1,10 @@
1
+ import postscribe from 'postscribe';
2
+
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
9
+ });
10
+ }