prebid-universal-creative 1.15.0 → 1.17.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.
- package/.circleci/config.yml +44 -30
- package/.github/workflows/codeql.yml +98 -0
- package/.github/workflows/issue_tracker.yml +32 -16
- package/README.md +4 -2
- package/dist/amp.js +3 -3
- package/dist/banner.js +3 -3
- package/dist/caf7688498213fb0c19f.max.js +1046 -0
- package/dist/creative.js +3 -3
- package/dist/load-cookie-with-consent.html +1 -1
- package/dist/load-cookie.html +1 -1
- package/dist/mobile.js +3 -3
- package/dist/native-render.js +3 -3
- package/dist/native-trk.js +3 -3
- package/dist/native.js +3 -3
- package/dist/uid.js +2 -2
- package/dist/video.js +3 -3
- package/gulpfile.js +15 -31
- package/integ-test/fixtures/test.js +79 -0
- package/integ-test/pages/amp.html +80 -0
- package/integ-test/pages/banner.html +96 -0
- package/integ-test/pages/native_legacy.html +107 -0
- package/integ-test/spec/amp_spec.js +111 -0
- package/integ-test/spec/banner_spec.js +85 -0
- package/integ-test/spec/native_legacy_spec.js +213 -0
- package/karma.conf.maker.js +4 -6
- package/package.json +10 -16
- package/playwright.config.js +108 -0
- package/src/adHtmlRender.js +11 -0
- package/src/cookieSync.js +3 -0
- package/src/cookieSyncWithConsent.js +3 -0
- package/src/domHelper.js +25 -15
- package/src/dynamicRenderer.js +56 -0
- package/src/messaging.js +23 -2
- package/src/mobileAndAmpRender.js +17 -20
- package/src/nativeAssetManager.js +134 -80
- package/src/nativeORTBTrackerManager.js +3 -3
- package/src/nativeRenderManager.js +44 -72
- package/src/nativeTrackerManager.js +2 -2
- package/src/renderingManager.js +17 -18
- package/src/utils.js +0 -9
- package/test/helpers/mocks.js +1 -0
- package/test/spec/dynamicRenderer_spec.js +167 -0
- package/test/spec/messaging_spec.js +98 -3
- package/test/spec/mobileAndAmpRender_spec.js +53 -63
- package/test/spec/nativeAssetManager_spec.js +290 -93
- package/test/spec/nativeORTBTrackerManager_spec.js +3 -19
- package/test/spec/nativeRenderManager_spec.js +77 -56
- package/test/spec/renderingManager_spec.js +20 -6
- package/webpack.conf.js +0 -1
- package/.nvmrc +0 -1
- package/dist/creative.max.js +0 -3101
- package/src/postscribeRender.js +0 -8
- package/test/e2e/specs/hello_world_banner_non_sf.spec.js +0 -14
- package/test/e2e/specs/outstream_non_sf.spec.js +0 -14
- package/test/e2e/specs/outstream_sf.spec.js +0 -14
- package/wdio.conf.js +0 -50
package/test/helpers/mocks.js
CHANGED
@@ -0,0 +1,167 @@
|
|
1
|
+
import {makeIframe} from '../../src/domHelper.js';
|
2
|
+
import {hasDynamicRenderer, MIN_RENDERER_VERSION, runDynamicRenderer} from '../../src/dynamicRenderer.js';
|
3
|
+
import {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, PREBID_EVENT} from '../../src/messaging.js';
|
4
|
+
|
5
|
+
describe('hasDynamicRenderer', () => {
|
6
|
+
Object.entries({
|
7
|
+
'neither': {},
|
8
|
+
'renderer, but no version': {
|
9
|
+
renderer: 'mock-renderer'
|
10
|
+
},
|
11
|
+
'renderer, but version is too low': {
|
12
|
+
renderer: 'mock-renderer',
|
13
|
+
rendererVersion: 1
|
14
|
+
},
|
15
|
+
}).forEach(([t, data]) => {
|
16
|
+
it(`returns false with ${t}`, () => {
|
17
|
+
expect(hasDynamicRenderer(data)).to.be.false;
|
18
|
+
})
|
19
|
+
});
|
20
|
+
|
21
|
+
it('returns true when both renderer and version are present', () => {
|
22
|
+
expect(hasDynamicRenderer({
|
23
|
+
renderer: 'mock-renderer',
|
24
|
+
rendererVersion: MIN_RENDERER_VERSION
|
25
|
+
})).to.be.true;
|
26
|
+
})
|
27
|
+
})
|
28
|
+
|
29
|
+
describe('runDynamicRenderer', () => {
|
30
|
+
let sendMessage, frame, render;
|
31
|
+
const adId = '123';
|
32
|
+
beforeEach(() => {
|
33
|
+
render = sinon.stub();
|
34
|
+
sendMessage = sinon.stub();
|
35
|
+
frame = makeIframe(document);
|
36
|
+
return new Promise((resolve) => {
|
37
|
+
frame.onload = resolve;
|
38
|
+
document.body.appendChild(frame);
|
39
|
+
}).then(() => {
|
40
|
+
frame.contentWindow._render = render;
|
41
|
+
});
|
42
|
+
});
|
43
|
+
|
44
|
+
afterEach(() => {
|
45
|
+
document.body.removeChild(frame);
|
46
|
+
});
|
47
|
+
|
48
|
+
function runRenderer(data) {
|
49
|
+
return runDynamicRenderer(adId, Object.assign({
|
50
|
+
renderer: `window.render = window.parent._render`
|
51
|
+
}, data), sendMessage, frame.contentWindow).catch(() => null);
|
52
|
+
}
|
53
|
+
|
54
|
+
it('runs renderer', () => {
|
55
|
+
const data = {ad: 'markup'};
|
56
|
+
return runRenderer(data).then(() => {
|
57
|
+
sinon.assert.calledWith(render, sinon.match(data), sinon.match({mkFrame: makeIframe}), frame.contentWindow);
|
58
|
+
});
|
59
|
+
});
|
60
|
+
|
61
|
+
Object.entries({
|
62
|
+
'returns': null,
|
63
|
+
'returns a promise that resolves': Promise.resolve()
|
64
|
+
}).forEach(([t, ret]) => {
|
65
|
+
it(`emits AD_RENDER_SUCCEDED when renderer ${t}`, () => {
|
66
|
+
render.callsFake(() => ret);
|
67
|
+
return runRenderer().then(() => {
|
68
|
+
sinon.assert.calledWith(sendMessage, {
|
69
|
+
adId,
|
70
|
+
message: PREBID_EVENT,
|
71
|
+
event: AD_RENDER_SUCCEEDED
|
72
|
+
});
|
73
|
+
});
|
74
|
+
});
|
75
|
+
});
|
76
|
+
|
77
|
+
describe('emits AD_RENDER_FAILED', () => {
|
78
|
+
Object.entries({
|
79
|
+
throws: (ret) => {
|
80
|
+
throw ret;
|
81
|
+
},
|
82
|
+
'returns a promise that rejects': (ret) => Promise.reject(ret)
|
83
|
+
}).forEach(([t, transform]) => {
|
84
|
+
describe(`when renderer ${t}`, () => {
|
85
|
+
Object.entries({
|
86
|
+
'error': {
|
87
|
+
ret: new Error('error message'),
|
88
|
+
info: {
|
89
|
+
reason: 'exception',
|
90
|
+
message: 'error message'
|
91
|
+
}
|
92
|
+
},
|
93
|
+
'error with reason': {
|
94
|
+
ret: {
|
95
|
+
reason: 'failure',
|
96
|
+
message: 'error message'
|
97
|
+
},
|
98
|
+
info: {
|
99
|
+
reason: 'failure',
|
100
|
+
message: 'error message'
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}).forEach(([t, {ret, info}]) => {
|
104
|
+
it(`an ${t}`, () => {
|
105
|
+
render.callsFake(() => transform(ret));
|
106
|
+
return runRenderer().then(() => {
|
107
|
+
sinon.assert.calledWith(sendMessage, {
|
108
|
+
adId,
|
109
|
+
message: PREBID_EVENT,
|
110
|
+
event: AD_RENDER_FAILED,
|
111
|
+
info
|
112
|
+
});
|
113
|
+
});
|
114
|
+
});
|
115
|
+
});
|
116
|
+
});
|
117
|
+
});
|
118
|
+
});
|
119
|
+
|
120
|
+
describe('renderer sendMessage', () => {
|
121
|
+
let rndSendMessage;
|
122
|
+
beforeEach(() => {
|
123
|
+
render.callsFake(() => new Promise())
|
124
|
+
return new Promise((resolve) => {
|
125
|
+
render.callsFake((_, {sendMessage}) => {
|
126
|
+
rndSendMessage = sendMessage;
|
127
|
+
resolve();
|
128
|
+
});
|
129
|
+
runRenderer();
|
130
|
+
})
|
131
|
+
});
|
132
|
+
it('adds message type and adId', () => {
|
133
|
+
rndSendMessage('type', {msg: 'data'});
|
134
|
+
sinon.assert.calledWith(sendMessage, {
|
135
|
+
message: 'type',
|
136
|
+
adId,
|
137
|
+
msg: 'data'
|
138
|
+
});
|
139
|
+
});
|
140
|
+
it('accepts response listeners', () => {
|
141
|
+
const listener = sinon.stub();
|
142
|
+
sendMessage.callsFake((_, responseListener) => {
|
143
|
+
responseListener('response');
|
144
|
+
})
|
145
|
+
rndSendMessage('msg', {}, listener);
|
146
|
+
sinon.assert.calledWith(listener, 'response');
|
147
|
+
});
|
148
|
+
|
149
|
+
it('emits AD_RENDER_FAILED if listener throws', () => {
|
150
|
+
const listener = sinon.stub().callsFake(() => { throw new Error('err') });
|
151
|
+
sendMessage.callsFake((_, responseListener) => {
|
152
|
+
responseListener && responseListener('response');
|
153
|
+
});
|
154
|
+
rndSendMessage('msg', {}, listener);
|
155
|
+
sinon.assert.calledWith(sendMessage, {
|
156
|
+
message: PREBID_EVENT,
|
157
|
+
event: AD_RENDER_FAILED,
|
158
|
+
adId,
|
159
|
+
info: {
|
160
|
+
reason: 'exception',
|
161
|
+
message: 'err'
|
162
|
+
}
|
163
|
+
})
|
164
|
+
})
|
165
|
+
});
|
166
|
+
|
167
|
+
});
|
@@ -1,5 +1,11 @@
|
|
1
1
|
import {mocks} from '../helpers/mocks.js';
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
AD_RENDER_FAILED,
|
4
|
+
AD_RENDER_SUCCEEDED,
|
5
|
+
PREBID_EVENT,
|
6
|
+
prebidMessenger,
|
7
|
+
renderEventMessage
|
8
|
+
} from '../../src/messaging.js';
|
3
9
|
|
4
10
|
describe('prebidMessenger',() => {
|
5
11
|
let win;
|
@@ -25,7 +31,6 @@ describe('prebidMessenger',() => {
|
|
25
31
|
describe('when publisher URL is available', () => {
|
26
32
|
const URL = 'https://www.publisher.com/page.html';
|
27
33
|
const ORIGIN = 'https://www.publisher.com'
|
28
|
-
let sendMessage;
|
29
34
|
let callback, handler;
|
30
35
|
|
31
36
|
beforeEach(() => {
|
@@ -33,15 +38,73 @@ describe('prebidMessenger',() => {
|
|
33
38
|
handler = h;
|
34
39
|
}
|
35
40
|
win.removeEventListener = sinon.spy();
|
36
|
-
sendMessage = prebidMessenger(URL, win);
|
37
41
|
callback = sinon.spy();
|
38
42
|
})
|
39
43
|
|
44
|
+
function sendMessage(...args) {
|
45
|
+
return prebidMessenger(URL, win)(...args);
|
46
|
+
}
|
47
|
+
|
40
48
|
it('should use origin for postMessage', () => {
|
41
49
|
sendMessage('test');
|
42
50
|
sinon.assert.calledWith(win.parent.postMessage, JSON.stringify('test'), ORIGIN);
|
43
51
|
});
|
44
52
|
|
53
|
+
describe('when window has multiple ancestors', () => {
|
54
|
+
let target;
|
55
|
+
beforeEach(() => {
|
56
|
+
const top = mocks.createFakeWindow('top');
|
57
|
+
target = {
|
58
|
+
...win.parent,
|
59
|
+
frames: {},
|
60
|
+
parent: {
|
61
|
+
top,
|
62
|
+
frames: {},
|
63
|
+
parent: top
|
64
|
+
}
|
65
|
+
};
|
66
|
+
win = {
|
67
|
+
top,
|
68
|
+
frames: {},
|
69
|
+
parent: {
|
70
|
+
top,
|
71
|
+
frames: {},
|
72
|
+
parent: target
|
73
|
+
}
|
74
|
+
};
|
75
|
+
})
|
76
|
+
Object.entries({
|
77
|
+
throws() { throw new DOMException() },
|
78
|
+
'does not throw'() { return {} }
|
79
|
+
}).forEach(([t, getFrames]) => {
|
80
|
+
describe(`when ancestor ${t}`, () => {
|
81
|
+
beforeEach(() => {
|
82
|
+
Object.defineProperty(target.parent.parent, 'frames', {get: getFrames});
|
83
|
+
})
|
84
|
+
it('should post to first ancestor that has a __pb_locator__ child', () => {
|
85
|
+
[target, target.parent].forEach(win => {
|
86
|
+
win.frames = {
|
87
|
+
__pb_locator__: {}
|
88
|
+
};
|
89
|
+
})
|
90
|
+
sendMessage('test');
|
91
|
+
sinon.assert.called(target.postMessage);
|
92
|
+
});
|
93
|
+
})
|
94
|
+
})
|
95
|
+
it('should post to immediate parent when no ancestor has __pb_locator__', () => {
|
96
|
+
win.parent.postMessage = sinon.spy();
|
97
|
+
delete target.postMessage;
|
98
|
+
sendMessage('test');
|
99
|
+
sinon.assert.called(win.parent.postMessage);
|
100
|
+
});
|
101
|
+
it('should post to first restricted frame if no __pb_locator__ can be found', () => {
|
102
|
+
Object.defineProperty(target, 'frames', {get() { throw new DOMException() }});
|
103
|
+
sendMessage('test');
|
104
|
+
sinon.assert.called(target.postMessage)
|
105
|
+
})
|
106
|
+
});
|
107
|
+
|
45
108
|
it('should not run callback on response if origin does not mach', ()=> {
|
46
109
|
sendMessage('test', callback);
|
47
110
|
handler({origin: 'different'});
|
@@ -62,3 +125,35 @@ describe('prebidMessenger',() => {
|
|
62
125
|
|
63
126
|
});
|
64
127
|
})
|
128
|
+
|
129
|
+
describe('renderEventMessage', () => {
|
130
|
+
Object.entries({
|
131
|
+
'success': {
|
132
|
+
input: {adId: '123'},
|
133
|
+
output: {
|
134
|
+
event: AD_RENDER_SUCCEEDED
|
135
|
+
}
|
136
|
+
},
|
137
|
+
'failure': {
|
138
|
+
input: {
|
139
|
+
adId: '321',
|
140
|
+
errorInfo: {
|
141
|
+
reason: 'failureReason',
|
142
|
+
message: 'error message'
|
143
|
+
}
|
144
|
+
},
|
145
|
+
output: {
|
146
|
+
event: AD_RENDER_FAILED,
|
147
|
+
info: {
|
148
|
+
reason: 'failureReason',
|
149
|
+
message: 'error message'
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}).forEach(([t, {input: {adId, errorInfo}, output}]) => {
|
154
|
+
Object.assign(output, {message: PREBID_EVENT, adId});
|
155
|
+
it(t, () => {
|
156
|
+
expect(renderEventMessage(adId, errorInfo)).to.eql(output);
|
157
|
+
})
|
158
|
+
})
|
159
|
+
})
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { renderAmpOrMobileAd } from 'src/mobileAndAmpRender';
|
2
|
-
import * as postscribeRender from 'src/
|
2
|
+
import * as postscribeRender from 'src/adHtmlRender'
|
3
3
|
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/adHtmlRender';
|
7
8
|
|
8
9
|
|
9
10
|
function renderingMocks() {
|
@@ -187,51 +188,58 @@ describe("renderingManager", function () {
|
|
187
188
|
});
|
188
189
|
|
189
190
|
describe("amp creative", function () {
|
191
|
+
let sandbox;
|
190
192
|
let writeHtmlSpy;
|
191
193
|
let sendRequestSpy;
|
192
194
|
let triggerPixelSpy;
|
193
195
|
let mockWin;
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
});
|
204
|
-
|
205
|
-
afterEach(function () {
|
206
|
-
writeHtmlSpy.resetHistory();
|
207
|
-
sendRequestSpy.resetHistory();
|
208
|
-
triggerPixelSpy.resetHistory();
|
209
|
-
});
|
210
|
-
|
211
|
-
after(function () {
|
212
|
-
writeHtmlSpy.restore();
|
213
|
-
sendRequestSpy.restore();
|
214
|
-
triggerPixelSpy.restore();
|
215
|
-
});
|
216
|
-
|
217
|
-
it("should render amp creative", function () {
|
218
|
-
let ucTagData = {
|
196
|
+
let ucTagData;
|
197
|
+
let response;
|
198
|
+
|
199
|
+
beforeEach(function () {
|
200
|
+
sandbox = sinon.sandbox.create();
|
201
|
+
writeHtmlSpy = sandbox.spy(postscribeRender, "writeAdHtml");
|
202
|
+
sendRequestSpy = sandbox.spy(utils, "sendRequest");
|
203
|
+
triggerPixelSpy = sandbox.spy(utils, "triggerPixel");
|
204
|
+
ucTagData = {
|
219
205
|
cacheHost: "example.com",
|
220
206
|
cachePath: "/path",
|
221
207
|
uuid: "123",
|
222
208
|
size: "300x250",
|
223
|
-
hbPb: "10.00",
|
224
209
|
};
|
225
|
-
|
226
|
-
renderAmpOrMobileAd(ucTagData);
|
227
|
-
|
228
|
-
let response = {
|
210
|
+
response = {
|
229
211
|
width: 300,
|
230
212
|
height: 250,
|
231
213
|
crid: 123,
|
232
214
|
adm: "ad-markup${AUCTION_PRICE}",
|
233
215
|
wurl: "https://test.prebidcache.wurl",
|
234
216
|
};
|
217
|
+
});
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
afterEach(function () {
|
222
|
+
sandbox.restore();
|
223
|
+
});
|
224
|
+
|
225
|
+
it('should send embed-resize message', () => {
|
226
|
+
sandbox.spy(window.parent, 'postMessage');
|
227
|
+
ucTagData.size = '400x500'
|
228
|
+
renderAmpOrMobileAd(ucTagData);
|
229
|
+
requests[0].respond(200, {}, JSON.stringify(response));
|
230
|
+
sinon.assert.calledWith(window.parent.postMessage, {
|
231
|
+
sentinel: "amp",
|
232
|
+
type: "embed-size",
|
233
|
+
width: 400,
|
234
|
+
height: 500,
|
235
|
+
});
|
236
|
+
})
|
237
|
+
|
238
|
+
it("should render amp creative", function () {
|
239
|
+
ucTagData.hbPb = "10.00";
|
240
|
+
renderAmpOrMobileAd(ucTagData);
|
241
|
+
|
242
|
+
|
235
243
|
requests[0].respond(200, {}, JSON.stringify(response));
|
236
244
|
expect(writeHtmlSpy.args[0][0]).to.equal(
|
237
245
|
"<!--Creative 123 served by Prebid.js Header Bidding-->ad-markup10.00"
|
@@ -245,24 +253,8 @@ describe("renderingManager", function () {
|
|
245
253
|
});
|
246
254
|
|
247
255
|
it("should replace AUCTION_PRICE with response.price over hbPb", function () {
|
248
|
-
let ucTagData = {
|
249
|
-
cacheHost: "example.com",
|
250
|
-
cachePath: "/path",
|
251
|
-
uuid: "123",
|
252
|
-
size: "300x250",
|
253
|
-
hbPb: "10.00",
|
254
|
-
};
|
255
|
-
|
256
256
|
renderAmpOrMobileAd(ucTagData);
|
257
|
-
|
258
|
-
let response = {
|
259
|
-
width: 300,
|
260
|
-
height: 250,
|
261
|
-
crid: 123,
|
262
|
-
price: 12.5,
|
263
|
-
adm: "ad-markup${AUCTION_PRICE}",
|
264
|
-
wurl: "https://test.prebidcache.wurl",
|
265
|
-
};
|
257
|
+
response.price = 12.5;
|
266
258
|
requests[0].respond(200, {}, JSON.stringify(response));
|
267
259
|
expect(writeHtmlSpy.args[0][0]).to.equal(
|
268
260
|
"<!--Creative 123 served by Prebid.js Header Bidding-->ad-markup12.5"
|
@@ -276,22 +268,7 @@ describe("renderingManager", function () {
|
|
276
268
|
});
|
277
269
|
|
278
270
|
it("should replace AUCTION_PRICE with with empty value when neither price nor hbPb exist", function () {
|
279
|
-
let ucTagData = {
|
280
|
-
cacheHost: "example.com",
|
281
|
-
cachePath: "/path",
|
282
|
-
uuid: "123",
|
283
|
-
size: "300x250",
|
284
|
-
};
|
285
|
-
|
286
271
|
renderAmpOrMobileAd(ucTagData);
|
287
|
-
|
288
|
-
let response = {
|
289
|
-
width: 300,
|
290
|
-
height: 250,
|
291
|
-
crid: 123,
|
292
|
-
adm: "ad-markup${AUCTION_PRICE}",
|
293
|
-
wurl: "https://test.prebidcache.wurl",
|
294
|
-
};
|
295
272
|
requests[0].respond(200, {}, JSON.stringify(response));
|
296
273
|
expect(writeHtmlSpy.args[0][0]).to.equal(
|
297
274
|
"<!--Creative 123 served by Prebid.js Header Bidding-->ad-markup"
|
@@ -306,3 +283,16 @@ describe("renderingManager", function () {
|
|
306
283
|
});
|
307
284
|
});
|
308
285
|
|
286
|
+
describe('writeAdHtml', () => {
|
287
|
+
it('removes DOCTYPE from markup', () => {
|
288
|
+
const ps = sinon.stub();
|
289
|
+
writeAdHtml('<!DOCTYPE html><div>mock-ad</div>', ps);
|
290
|
+
sinon.assert.calledWith(ps, sinon.match.any, '<div>mock-ad</div>')
|
291
|
+
});
|
292
|
+
|
293
|
+
it('removes lowercase doctype from markup', () => {
|
294
|
+
const ps = sinon.stub();
|
295
|
+
writeAdHtml('<!doctype html><div>mock-ad</div>', ps);
|
296
|
+
sinon.assert.calledWith(ps, sinon.match.any, '<div>mock-ad</div>')
|
297
|
+
});
|
298
|
+
})
|