prebid-universal-creative 1.14.2 → 1.15.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.
@@ -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',
@@ -328,7 +327,7 @@ export function newNativeAssetManager(win, pubUrl) {
328
327
  } else if ((data.hasOwnProperty('adTemplate') && data.adTemplate)||(hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate'))) {
329
328
  const template = (hasPbNativeData() && win.pbNativeData.hasOwnProperty('adTemplate') && win.pbNativeData.adTemplate) || data.adTemplate;
330
329
  const newHtml = replace(template, data);
331
-
330
+
332
331
  renderAd(newHtml, data);
333
332
  } else {
334
333
  const newHtml = replace(body, data);
@@ -350,7 +349,7 @@ export function newNativeAssetManager(win, pubUrl) {
350
349
  if (!currentParentWindow.frames || !currentParentWindow.frames.length) return null;
351
350
  for (let idx = 0; idx < currentParentWindow.frames.length; idx++)
352
351
  if (currentParentWindow.frames[idx] === currentWindow) {
353
- if (!currentParentWindow.document) return null;
352
+ if (!currentParentWindow.document) return null;
354
353
  for (let frameElement of currentParentWindow.document.getElementsByTagName('iframe')) {
355
354
  if (!frameElement.contentWindow) return null;
356
355
  if (frameElement.contentWindow === currentWindow) {
@@ -366,10 +365,10 @@ export function newNativeAssetManager(win, pubUrl) {
366
365
  // current iframe width to the width of the container. This
367
366
  // is to handle the case where the native ad is rendered inside
368
367
  // a GAM display ad.
369
- if (!envionment.isSafeFrame()) {
368
+ if (!isSafeFrame(window)) {
370
369
  let iframeContainer = getCurrentFrameContainer(win);
371
370
  if (iframeContainer && iframeContainer.children && iframeContainer.children[0]) {
372
- const iframe = iframeContainer.children[0];
371
+ const iframe = iframeContainer.children[0];
373
372
  if (iframe.width === '1' && iframe.height === '1') {
374
373
  let width = iframeContainer.getBoundingClientRect().width;
375
374
  win.document.body.style.width = `${width}px`;
@@ -421,7 +420,7 @@ export function newNativeAssetManager(win, pubUrl) {
421
420
  if (ortb.link) {
422
421
  html = html.replaceAll("##hb_native_linkurl##", ortb.link.url);
423
422
  }
424
-
423
+
425
424
  return html;
426
425
  }
427
426
 
@@ -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;
@@ -1,7 +1,7 @@
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
7
  const AD_ANCHOR_CLASS_NAME = 'pb-click';
@@ -48,7 +48,7 @@ export function newNativeRenderManager(win) {
48
48
  }
49
49
 
50
50
  // START OF MAIN CODE
51
- let renderNativeAd = function(nativeTag) {
51
+ let renderNativeAd = function(doc, nativeTag) {
52
52
  window.pbNativeData = nativeTag;
53
53
  sendMessage = prebidMessenger(nativeTag.pubUrl, win);
54
54
  const nativeAssetManager = newNativeAssetManager(window, nativeTag.pubUrl);
@@ -56,17 +56,17 @@ export function newNativeRenderManager(win) {
56
56
  if (nativeTag.hasOwnProperty('adId')) {
57
57
 
58
58
  if (nativeTag.hasOwnProperty('rendererUrl') && !nativeTag.rendererUrl.match(/##.*##/i)) {
59
- const scr = document.createElement('SCRIPT');
59
+ const scr = doc.createElement('SCRIPT');
60
60
  scr.src = nativeTag.rendererUrl,
61
61
  scr.id = 'pb-native-renderer';
62
- document.body.appendChild(scr);
62
+ doc.body.appendChild(scr);
63
63
  }
64
64
  nativeAssetManager.loadAssets(nativeTag.adId, () => {
65
65
  fireNativeImpTracker(nativeTag.adId);
66
66
  fireNativeCallback();
67
67
  });
68
68
  } else {
69
- console.warn('Prebid Native Tag object was missing \'adId\'.');
69
+ console.warn("Prebid Native Tag object was missing 'adId'.");
70
70
  }
71
71
  }
72
72
 
@@ -0,0 +1,8 @@
1
+ import postscribe from 'postscribe';
2
+
3
+ export function writeAdHtml(markup) {
4
+ postscribe(document.body, markup, {
5
+ error: console.error
6
+ });
7
+ }
8
+