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
@@ -0,0 +1,213 @@
|
|
1
|
+
import {test, expect} from '../fixtures/test.js';
|
2
|
+
|
3
|
+
test.describe('Legacy native', () => {
|
4
|
+
|
5
|
+
const TRACKER_URL = 'https://www.tracker.com/';
|
6
|
+
const TRACKERS = Object.fromEntries(
|
7
|
+
['imp', 'js', 'click'].map(ttype => [ttype, `${TRACKER_URL}${ttype}`])
|
8
|
+
)
|
9
|
+
|
10
|
+
const TEMPLATE = `
|
11
|
+
<div id="the-ad">
|
12
|
+
<a class="clickUrl" href="##hb_native_linkurl##">Click</a>
|
13
|
+
<img class="image" width="100" src="##hb_native_image##" />
|
14
|
+
<div class="title pb-click">##hb_native_title##</div>
|
15
|
+
<div class="body">##hb_native_body##</div>
|
16
|
+
</div>
|
17
|
+
`;
|
18
|
+
|
19
|
+
const RENDERER_URL = 'https://www.custom-renderer.com/renderer.js';
|
20
|
+
|
21
|
+
function customRenderer(data) {
|
22
|
+
const assets = Object.fromEntries(data.map((d) => [d.key, d.value]))
|
23
|
+
return `
|
24
|
+
<div id="the-ad">
|
25
|
+
<a class="clickUrl" href="${assets.clickUrl}">Click</a>
|
26
|
+
<img class="image" width="100" src="${assets.image}" />
|
27
|
+
<div class="title pb-click">${assets.title}</div>
|
28
|
+
<div class="body">${assets.body}</div>
|
29
|
+
</div>
|
30
|
+
`
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
const ASSETS = {
|
35
|
+
image: {
|
36
|
+
value: 'https://prebid.org/wp-content/uploads/2021/02/Prebid-Logo-RGB-Full-Color-Medium.svg',
|
37
|
+
expect(e) {
|
38
|
+
return e.toHaveAttribute('src', this.value)
|
39
|
+
}
|
40
|
+
},
|
41
|
+
title: {
|
42
|
+
value: 'Ad title',
|
43
|
+
expect(e) {
|
44
|
+
return e.toHaveText(this.value)
|
45
|
+
}
|
46
|
+
},
|
47
|
+
body: {
|
48
|
+
value: 'Ad body',
|
49
|
+
expect(e) {
|
50
|
+
return e.toHaveText(this.value)
|
51
|
+
}
|
52
|
+
},
|
53
|
+
clickUrl: {
|
54
|
+
value: 'https://some-link.com',
|
55
|
+
expect(e) {
|
56
|
+
return e.toHaveAttribute('href', this.value)
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
let trackersFired;
|
62
|
+
|
63
|
+
test.beforeEach(async ({page}) => {
|
64
|
+
trackersFired = {};
|
65
|
+
await page.route((u) => u.href.startsWith(TRACKER_URL), async (route, req) => {
|
66
|
+
const ttype = req.url().substring(TRACKER_URL.length);
|
67
|
+
trackersFired[ttype] = true;
|
68
|
+
await route.fulfill({});
|
69
|
+
});
|
70
|
+
await page.route((u) => u.href.startsWith(RENDERER_URL), async (route) => {
|
71
|
+
await route.fulfill({
|
72
|
+
contentType: 'application/javascript',
|
73
|
+
body: `window.renderAd = ${customRenderer.toString()};`
|
74
|
+
})
|
75
|
+
})
|
76
|
+
});
|
77
|
+
|
78
|
+
|
79
|
+
function bidResponse(native) {
|
80
|
+
return {
|
81
|
+
ad: null,
|
82
|
+
adId: 'mock-ad',
|
83
|
+
native
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
function getCreative(isBanner, isTemplateInCreative, isSafeFrame) {
|
88
|
+
if (!isBanner) {
|
89
|
+
return isTemplateInCreative ? 'native-legacy' : 'native-no-template'
|
90
|
+
} else {
|
91
|
+
return (isTemplateInCreative ? 'native-banner-legacy' : 'native-banner-no-template') + (isSafeFrame ? '' : '-no-frame');
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
|
96
|
+
Object.entries({
|
97
|
+
'legacy response': {
|
98
|
+
...Object.fromEntries(Object.entries(ASSETS).map(([name, asset]) => [name, asset.value])),
|
99
|
+
javascriptTrackers: [`<script src="${TRACKERS.js}"></script>`],
|
100
|
+
impressionTrackers: [TRACKERS.imp],
|
101
|
+
clickTrackers: [TRACKERS.click],
|
102
|
+
},
|
103
|
+
'ortb response': {
|
104
|
+
ortb: {
|
105
|
+
ver: '1.2',
|
106
|
+
link: {
|
107
|
+
url: ASSETS.clickUrl.value,
|
108
|
+
clicktrackers: [
|
109
|
+
TRACKERS.click
|
110
|
+
],
|
111
|
+
},
|
112
|
+
jstracker: `<script src="${TRACKERS.js}"></script>`,
|
113
|
+
eventtrackers: [
|
114
|
+
{
|
115
|
+
url: TRACKERS.imp,
|
116
|
+
event: 1,
|
117
|
+
method: 1
|
118
|
+
}
|
119
|
+
],
|
120
|
+
assets: [
|
121
|
+
{
|
122
|
+
id: 0,
|
123
|
+
img: {
|
124
|
+
url: ASSETS.image.value,
|
125
|
+
}
|
126
|
+
},
|
127
|
+
{
|
128
|
+
id: 1,
|
129
|
+
title: {
|
130
|
+
text: ASSETS.title.value
|
131
|
+
}
|
132
|
+
},
|
133
|
+
{
|
134
|
+
id: 2,
|
135
|
+
data: {
|
136
|
+
value: ASSETS.body.value
|
137
|
+
}
|
138
|
+
}
|
139
|
+
]
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}).forEach(([t, native]) => {
|
143
|
+
test.describe(t, () => {
|
144
|
+
Object.entries({
|
145
|
+
'native proper': false,
|
146
|
+
'native in banner': true
|
147
|
+
}).forEach(([t, isBanner]) => {
|
148
|
+
test.describe(t, () => {
|
149
|
+
Object.entries({
|
150
|
+
'template in creative': {
|
151
|
+
isTemplateInCreative: true,
|
152
|
+
},
|
153
|
+
'adTemplate': {
|
154
|
+
adTemplate: TEMPLATE
|
155
|
+
},
|
156
|
+
'custom renderer': {
|
157
|
+
rendererUrl: RENDERER_URL
|
158
|
+
}
|
159
|
+
}).forEach(([t, {isTemplateInCreative, adTemplate, rendererUrl}]) => {
|
160
|
+
test.describe(t, () => {
|
161
|
+
Object.entries({
|
162
|
+
'safeframe': true,
|
163
|
+
'non safeframe': false,
|
164
|
+
}).forEach(([t, isSafeFrame]) => {
|
165
|
+
if (!isSafeFrame && !isBanner) return; // there's no option to run GAM native ads without safeframe
|
166
|
+
|
167
|
+
test.describe(t, () => {
|
168
|
+
test.beforeEach(async ({page}) => {
|
169
|
+
await page.goto(`native_legacy.html?creative=${getCreative(isBanner, isTemplateInCreative, isSafeFrame)}&banner=${isBanner}&bidResponse=${encodeURIComponent(JSON.stringify(bidResponse({...native, adTemplate, rendererUrl})))}`);
|
170
|
+
});
|
171
|
+
|
172
|
+
test('should display ad', async ({crossLocator}) => {
|
173
|
+
await expect(await crossLocator('#the-ad')).toBeVisible();
|
174
|
+
});
|
175
|
+
|
176
|
+
test('should fill in assets', async ({crossLocator}) => {
|
177
|
+
await Promise.all(Object.entries(ASSETS).map(async ([name, a]) => await a.expect(expect(await crossLocator(`#the-ad .${name}`)))))
|
178
|
+
});
|
179
|
+
|
180
|
+
// TODO: should this emit AD_RENDER_SUCCEEDED? see https://github.com/prebid/prebid-universal-creative/issues/182
|
181
|
+
['bidWon'].forEach(ev => {
|
182
|
+
test(`should emit '${ev}'`, async ({expectEvent}) => {
|
183
|
+
await expectEvent(event => event.eventType === ev && event.args.adId === 'mock-ad')
|
184
|
+
})
|
185
|
+
});
|
186
|
+
|
187
|
+
['js', 'imp'].forEach(ttype => {
|
188
|
+
test(`should fire ${ttype} trackers`, async () => {
|
189
|
+
await expect.poll(() => trackersFired[ttype]).toBeTruthy();
|
190
|
+
})
|
191
|
+
})
|
192
|
+
|
193
|
+
test('should fire click trackers', async ({crossLocator, browserName}, testInfo) => {
|
194
|
+
if (browserName === 'webkit' && testInfo.project.use.headless !== false) {
|
195
|
+
// webkit does not like this test. It passes locally in headed mode:
|
196
|
+
// $ npx run playwright test --headed --workers 1 --project webkit -g "should fire click trackers"
|
197
|
+
// but I am unable to get headed tests to work on the pipeline
|
198
|
+
// (e.g. https://app.circleci.com/pipelines/github/prebid/prebid-universal-creative/309/workflows/b9bafe18-e2b3-4081-a2f0-e74b33575b56/jobs/573)
|
199
|
+
return;
|
200
|
+
}
|
201
|
+
const el = await crossLocator('#the-ad .pb-click');
|
202
|
+
await el.click();
|
203
|
+
await expect.poll(() => trackersFired.click).toBeTruthy();
|
204
|
+
});
|
205
|
+
});
|
206
|
+
});
|
207
|
+
});
|
208
|
+
});
|
209
|
+
});
|
210
|
+
});
|
211
|
+
});
|
212
|
+
});
|
213
|
+
});
|
package/karma.conf.maker.js
CHANGED
@@ -3,7 +3,7 @@ const webpackConf = require('./webpack.conf');
|
|
3
3
|
const karmaConstants = require('karma').constants;
|
4
4
|
const path = require('path');
|
5
5
|
|
6
|
-
function setBrowsers(karmaConf, browserstack
|
6
|
+
function setBrowsers(karmaConf, browserstack) {
|
7
7
|
if (browserstack) {
|
8
8
|
karmaConf.browserStack = {
|
9
9
|
username: process.env.BROWSERSTACK_USERNAME,
|
@@ -12,8 +12,6 @@ function setBrowsers(karmaConf, browserstack, watchMode) {
|
|
12
12
|
}
|
13
13
|
karmaConf.customLaunchers = require('./browsers.json')
|
14
14
|
karmaConf.browsers = Object.keys(karmaConf.customLaunchers);
|
15
|
-
} else if (watchMode) {
|
16
|
-
karmaConf.browsers = ['Chrome'];
|
17
15
|
}
|
18
16
|
}
|
19
17
|
|
@@ -29,7 +27,7 @@ function setReporters(karmaConf, codeCoverage, browserstack) {
|
|
29
27
|
suppressPassed: true
|
30
28
|
};
|
31
29
|
}
|
32
|
-
|
30
|
+
|
33
31
|
if (codeCoverage) {
|
34
32
|
karmaConf.reporters.push('coverage-istanbul');
|
35
33
|
karmaConf.coverageIstanbulReporter = {
|
@@ -41,7 +39,7 @@ function setReporters(karmaConf, codeCoverage, browserstack) {
|
|
41
39
|
urlFriendlyName: true, // simply replaces spaces with _ for files/dirs
|
42
40
|
}
|
43
41
|
}
|
44
|
-
}
|
42
|
+
}
|
45
43
|
}
|
46
44
|
}
|
47
45
|
|
@@ -149,6 +147,6 @@ module.exports = function(codeCoverage, browserstack, watchMode) {
|
|
149
147
|
captureTimeout: 4 * 60 * 1000, // default 60000
|
150
148
|
}
|
151
149
|
setReporters(config, codeCoverage, browserstack);
|
152
|
-
setBrowsers(config, browserstack
|
150
|
+
setBrowsers(config, browserstack);
|
153
151
|
return config;
|
154
152
|
}
|
package/package.json
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"name": "prebid-universal-creative",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.17.0",
|
4
4
|
"description": "Universal creative for Prebid apps",
|
5
5
|
"main": "dist/creative.js",
|
6
6
|
"scripts": {
|
7
|
-
"test": "
|
7
|
+
"test": "gulp test",
|
8
8
|
"prepublish": "gulp build"
|
9
9
|
},
|
10
10
|
"repository": {
|
@@ -26,13 +26,7 @@
|
|
26
26
|
"@babel/plugin-transform-modules-commonjs": "^7.6.0",
|
27
27
|
"@babel/preset-env": "^7.2.3",
|
28
28
|
"@babel/register": "^7.6.2",
|
29
|
-
"@
|
30
|
-
"@wdio/cli": "^7.25.4",
|
31
|
-
"@wdio/concise-reporter": "^7.25.4",
|
32
|
-
"@wdio/local-runner": "^7.25.4",
|
33
|
-
"@wdio/mocha-framework": "^7.25.4",
|
34
|
-
"@wdio/spec-reporter": "^7.25.4",
|
35
|
-
"@wdio/sync": "^7.25.4",
|
29
|
+
"@playwright/test": "^1.50.1",
|
36
30
|
"babel-loader": "^8.0.5",
|
37
31
|
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
38
32
|
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
@@ -41,7 +35,7 @@
|
|
41
35
|
"core-js-pure": "^3.13.0",
|
42
36
|
"del": "^5.0.0",
|
43
37
|
"execa": "^1.0.0",
|
44
|
-
"gulp": "^
|
38
|
+
"gulp": "^5.0.0",
|
45
39
|
"gulp-clean": "^0.4.0",
|
46
40
|
"gulp-connect": "^5.7.0",
|
47
41
|
"gulp-eslint": "^4.0.2",
|
@@ -58,25 +52,25 @@
|
|
58
52
|
"karma-chrome-launcher": "^2.2.0",
|
59
53
|
"karma-coverage": "^1.1.2",
|
60
54
|
"karma-coverage-istanbul-reporter": "^1.4.3",
|
61
|
-
"karma-mocha": "^
|
55
|
+
"karma-mocha": "^2.0.1",
|
62
56
|
"karma-mocha-reporter": "^2.2.5",
|
63
57
|
"karma-sinon": "^1.0.5",
|
64
58
|
"karma-sourcemap-loader": "^0.3.7",
|
65
59
|
"karma-spec-reporter": "^0.0.31",
|
66
60
|
"karma-webpack": "^3.0.5",
|
67
61
|
"lodash": "^4.17.14",
|
68
|
-
"mocha": "^
|
62
|
+
"mocha": "^10.2.0",
|
69
63
|
"opn": "^6.0.0",
|
70
64
|
"sinon": "^6.3.4",
|
71
65
|
"string-replace-webpack-plugin": "^0.1.3",
|
72
|
-
"webdriverio": "^
|
66
|
+
"webdriverio": "^9.10.0",
|
73
67
|
"webpack": "^3.12.0",
|
68
|
+
"webpack-common-shake": "^1.0.0",
|
74
69
|
"webpack-stream": "^4.0.0",
|
75
|
-
"yargs": "^11.0.0"
|
76
|
-
"webpack-common-shake": "^1.0.0"
|
70
|
+
"yargs": "^11.0.0"
|
77
71
|
},
|
78
72
|
"dependencies": {
|
79
73
|
"babel-plugin-transform-object-assign": "^6.22.0",
|
80
|
-
"
|
74
|
+
"gulp-cli": "^3.0.0"
|
81
75
|
}
|
82
76
|
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
// @ts-check
|
2
|
+
const { devices } = require('@playwright/test');
|
3
|
+
const {BASE_URL} = require('./integ-test/fixtures/test.js');
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Read environment variables from file.
|
7
|
+
* https://github.com/motdotla/dotenv
|
8
|
+
*/
|
9
|
+
// require('dotenv').config();
|
10
|
+
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @see https://playwright.dev/docs/test-configuration
|
14
|
+
* @type {import('@playwright/test').PlaywrightTestConfig}
|
15
|
+
*/
|
16
|
+
const config = {
|
17
|
+
testDir: './integ-test/spec',
|
18
|
+
testMatch: /.*.js/,
|
19
|
+
/* Maximum time one test can run for. */
|
20
|
+
timeout: 30 * 1000,
|
21
|
+
expect: {
|
22
|
+
/**
|
23
|
+
* Maximum time expect() should wait for the condition to be met.
|
24
|
+
* For example in `await expect(locator).toHaveText();`
|
25
|
+
*/
|
26
|
+
timeout: 15000
|
27
|
+
},
|
28
|
+
/* Run tests in files in parallel */
|
29
|
+
fullyParallel: true,
|
30
|
+
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
31
|
+
forbidOnly: !!process.env.CI,
|
32
|
+
/* Retry on CI only */
|
33
|
+
retries: process.env.CI ? 3 : 0,
|
34
|
+
workers: process.env.CI ? 10 : undefined,
|
35
|
+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
36
|
+
reporter: 'html',
|
37
|
+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
38
|
+
use: {
|
39
|
+
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
40
|
+
actionTimeout: 0,
|
41
|
+
/* Base URL to use in actions like `await page.goto('/')`. */
|
42
|
+
baseURL: BASE_URL,
|
43
|
+
|
44
|
+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
45
|
+
trace: 'on-first-retry',
|
46
|
+
},
|
47
|
+
|
48
|
+
/* Configure projects for major browsers */
|
49
|
+
projects: [
|
50
|
+
{
|
51
|
+
name: 'firefox',
|
52
|
+
use: {
|
53
|
+
...devices['Desktop Firefox'],
|
54
|
+
},
|
55
|
+
},
|
56
|
+
{
|
57
|
+
name: 'chromium',
|
58
|
+
use: {
|
59
|
+
...devices['Desktop Chrome'],
|
60
|
+
},
|
61
|
+
},
|
62
|
+
|
63
|
+
{
|
64
|
+
name: 'webkit',
|
65
|
+
use: {
|
66
|
+
...devices['Desktop Safari'],
|
67
|
+
},
|
68
|
+
},
|
69
|
+
/* Test against mobile viewports. */
|
70
|
+
// {
|
71
|
+
// name: 'Mobile Chrome',
|
72
|
+
// use: {
|
73
|
+
// ...devices['Pixel 5'],
|
74
|
+
// },
|
75
|
+
// },
|
76
|
+
// {
|
77
|
+
// name: 'Mobile Safari',
|
78
|
+
// use: {
|
79
|
+
// ...devices['iPhone 12'],
|
80
|
+
// },
|
81
|
+
// },
|
82
|
+
|
83
|
+
/* Test against branded browsers. */
|
84
|
+
// {
|
85
|
+
// name: 'Microsoft Edge',
|
86
|
+
// use: {
|
87
|
+
// channel: 'msedge',
|
88
|
+
// },
|
89
|
+
// },
|
90
|
+
// {
|
91
|
+
// name: 'Google Chrome',
|
92
|
+
// use: {
|
93
|
+
// channel: 'chrome',
|
94
|
+
// },
|
95
|
+
// },
|
96
|
+
],
|
97
|
+
|
98
|
+
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
99
|
+
// outputDir: 'test-results/',
|
100
|
+
|
101
|
+
/* Run your local dev server before starting the tests */
|
102
|
+
// webServer: {
|
103
|
+
// command: 'npm run start',
|
104
|
+
// port: 3000,
|
105
|
+
// },
|
106
|
+
};
|
107
|
+
|
108
|
+
module.exports = config;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export function writeAdHtml(markup, insertHTML = document.body.insertAdjacentHTML.bind(document.body)) {
|
2
|
+
// remove <?xml> and <!doctype> tags
|
3
|
+
// https://github.com/prebid/prebid-universal-creative/issues/134
|
4
|
+
markup = markup.replace(/\<(\?xml|(\!DOCTYPE[^\>\[]+(\[[^\]]+)?))+[^>]+\>/gi, '');
|
5
|
+
|
6
|
+
try {
|
7
|
+
insertHTML('beforeend', markup);
|
8
|
+
} catch (error) {
|
9
|
+
console.error(error);
|
10
|
+
}
|
11
|
+
}
|
package/src/cookieSync.js
CHANGED
@@ -20,6 +20,8 @@ const BIDDER_ARGS = sanitizeBidders(parseQueryParam('bidders', window.location.s
|
|
20
20
|
const IS_AMP = sanitizeSource(parseQueryParam('source', window.location.search));
|
21
21
|
const maxSyncCountParam = parseQueryParam('max_sync_count', window.location.search);
|
22
22
|
const MAX_SYNC_COUNT = sanitizeSyncCount(parseInt((maxSyncCountParam) ? maxSyncCountParam : 10, 10));
|
23
|
+
const coopSyncParam = parseQueryParam('coop_sync', window.location.search);
|
24
|
+
const COOP_SYNC = !coopSyncParam || coopSyncParam === 'true' || !!parseInt(coopSyncParam);
|
23
25
|
const GDPR = sanitizeGdpr(parseInt(parseQueryParam('gdpr', window.location.search), 10));
|
24
26
|
const GDPR_CONSENT = sanitizeGdprConsent(parseQueryParam('gdpr_consent', window.location.search));
|
25
27
|
|
@@ -254,6 +256,7 @@ function sanitizeBidders(value) {
|
|
254
256
|
function getStringifiedData(endPointArgs) {
|
255
257
|
var data = (endPointArgs && typeof endPointArgs === 'object') ? endPointArgs : {}
|
256
258
|
data['limit'] = MAX_SYNC_COUNT;
|
259
|
+
data['coopSync'] = COOP_SYNC;
|
257
260
|
|
258
261
|
if(GDPR) data.gdpr = GDPR;
|
259
262
|
if(GDPR_CONSENT) data.gdpr_consent = GDPR_CONSENT;
|
@@ -20,6 +20,8 @@ const IS_AMP = sanitizeSource(parseQueryParam('source', window.location.search))
|
|
20
20
|
const BIDDER_ARGS = sanitizeBidders(parseQueryParam('bidders', window.location.search));
|
21
21
|
const maxSyncCountParam = parseQueryParam('max_sync_count', window.location.search);
|
22
22
|
const MAX_SYNC_COUNT = sanitizeSyncCount(parseInt((maxSyncCountParam) ? maxSyncCountParam : 10, 10));
|
23
|
+
const coopSyncParam = parseQueryParam('coop_sync', window.location.search);
|
24
|
+
const COOP_SYNC = !coopSyncParam || coopSyncParam === 'true' || !!parseInt(coopSyncParam);
|
23
25
|
const TIMEOUT = sanitizeTimeout(parseInt(parseQueryParam('timeout', window.location.search), 10));
|
24
26
|
const DEFAULT_GDPR_SCOPE = sanitizeScope(parseInt(parseQueryParam('defaultGdprScope', window.location.search), 10));
|
25
27
|
|
@@ -273,6 +275,7 @@ function attachConsent(data) {
|
|
273
275
|
function getStringifiedData(endPointArgs) {
|
274
276
|
var data = (endPointArgs && typeof endPointArgs === 'object') ? endPointArgs : {}
|
275
277
|
data['limit'] = MAX_SYNC_COUNT;
|
278
|
+
data['coopSync'] = COOP_SYNC;
|
276
279
|
|
277
280
|
if(IS_AMP) data.filterSettings = {
|
278
281
|
iframe: {
|
package/src/domHelper.js
CHANGED
@@ -3,25 +3,35 @@
|
|
3
3
|
*/
|
4
4
|
|
5
5
|
|
6
|
+
|
7
|
+
/**
|
8
|
+
* returns a empty iframe element with specified attributes.
|
9
|
+
*/
|
10
|
+
export function makeIframe(doc, attrs = {}) {
|
11
|
+
const frame = doc.createElement('iframe');
|
12
|
+
Object.entries(Object.assign({
|
13
|
+
frameborder: 0,
|
14
|
+
scrolling: 'no',
|
15
|
+
marginheight: 0,
|
16
|
+
marginwidth: 0,
|
17
|
+
TOPMARGIN: 0,
|
18
|
+
LEFTMARGIN: 0,
|
19
|
+
allowtransparency: 'true'
|
20
|
+
}, attrs)).forEach(([attr, value]) => {
|
21
|
+
frame.setAttribute(attr, value);
|
22
|
+
});
|
23
|
+
return frame;
|
24
|
+
}
|
25
|
+
|
6
26
|
/**
|
7
27
|
* returns a empty iframe element with specified height/width
|
8
|
-
* @param {Number} height height iframe set to
|
28
|
+
* @param {Number} height height iframe set to
|
9
29
|
* @param {Number} width width iframe set to
|
10
|
-
* @returns {Element} iframe DOM element
|
30
|
+
* @returns {Element} iframe DOM element
|
11
31
|
*/
|
12
32
|
export function getEmptyIframe(height, width) {
|
13
|
-
|
14
|
-
|
15
|
-
frame.setAttribute('scrolling', 'no');
|
16
|
-
frame.setAttribute('marginheight', 0);
|
17
|
-
frame.setAttribute('marginwidth', 0);
|
18
|
-
frame.setAttribute('TOPMARGIN', 0);
|
19
|
-
frame.setAttribute('LEFTMARGIN', 0);
|
20
|
-
frame.setAttribute('allowtransparency', 'true');
|
21
|
-
frame.setAttribute('width', width);
|
22
|
-
frame.setAttribute('height', height);
|
23
|
-
return frame;
|
24
|
-
}
|
33
|
+
return makeIframe(document, {height, width})
|
34
|
+
}
|
25
35
|
|
26
36
|
/**
|
27
37
|
* Insert element to passed target
|
@@ -44,4 +54,4 @@ export function insertElement(elm, doc, target) {
|
|
44
54
|
elToAppend.insertBefore(elm, elToAppend.firstChild);
|
45
55
|
}
|
46
56
|
} catch (e) {}
|
47
|
-
}
|
57
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import {makeIframe} from './domHelper.js';
|
2
|
+
import {renderEventMessage} from './messaging.js';
|
3
|
+
|
4
|
+
export const MIN_RENDERER_VERSION = 3;
|
5
|
+
|
6
|
+
export function hasDynamicRenderer(message) {
|
7
|
+
return typeof message.renderer === 'string' && parseInt(message.rendererVersion, 10) >= MIN_RENDERER_VERSION
|
8
|
+
}
|
9
|
+
|
10
|
+
export function runDynamicRenderer(adId, data, sendMessage, win = window, mkFrame = makeIframe) {
|
11
|
+
const renderer = mkFrame(win.document, {
|
12
|
+
width: 0,
|
13
|
+
height: 0,
|
14
|
+
style: 'display: none',
|
15
|
+
srcdoc: `<script>${data.renderer}</script>`,
|
16
|
+
name: '__pb_renderer__'
|
17
|
+
});
|
18
|
+
|
19
|
+
return new Promise((resolve, reject) => {
|
20
|
+
function onError(e = {}) {
|
21
|
+
sendMessage(renderEventMessage(adId, {
|
22
|
+
reason: e.reason || 'exception',
|
23
|
+
message: e.message
|
24
|
+
}));
|
25
|
+
e.stack && console.error(e);
|
26
|
+
reject(e);
|
27
|
+
}
|
28
|
+
|
29
|
+
function guard(fn) {
|
30
|
+
return function () {
|
31
|
+
try {
|
32
|
+
return fn.apply(this, arguments);
|
33
|
+
} catch (e) {
|
34
|
+
onError(e);
|
35
|
+
}
|
36
|
+
};
|
37
|
+
}
|
38
|
+
|
39
|
+
renderer.onload = guard(function () {
|
40
|
+
const W = renderer.contentWindow;
|
41
|
+
// NOTE: on Firefox, `Promise.resolve(P)` or `new Promise((resolve) => resolve(P))`
|
42
|
+
// does not appear to work if P comes from another frame
|
43
|
+
W.Promise.resolve(W.render(data, {
|
44
|
+
mkFrame,
|
45
|
+
sendMessage: (type, payload, onResponse) => sendMessage(
|
46
|
+
Object.assign({adId, message: type}, payload),
|
47
|
+
onResponse ? guard(onResponse) : undefined
|
48
|
+
)
|
49
|
+
}, win)).then(
|
50
|
+
() => sendMessage(renderEventMessage(adId)),
|
51
|
+
onError
|
52
|
+
).then(resolve);
|
53
|
+
});
|
54
|
+
win.document.body.appendChild(renderer);
|
55
|
+
});
|
56
|
+
}
|
package/src/messaging.js
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
import {parseUrl} from './utils.js';
|
2
|
+
export const PREBID_EVENT = 'Prebid Event';
|
3
|
+
export const AD_RENDER_SUCCEEDED = 'adRenderSucceeded';
|
4
|
+
export const AD_RENDER_FAILED = 'adRenderFailed';
|
2
5
|
|
3
6
|
export function prebidMessenger(publisherURL, win = window) {
|
4
7
|
const prebidDomain = (() => {
|
@@ -9,6 +12,16 @@ export function prebidMessenger(publisherURL, win = window) {
|
|
9
12
|
return parsedUrl.protocol + '://' + parsedUrl.host;
|
10
13
|
})();
|
11
14
|
|
15
|
+
function isPrebidWindow(win) {
|
16
|
+
return win && win.frames && win.frames.__pb_locator__;
|
17
|
+
}
|
18
|
+
|
19
|
+
let target = win.parent;
|
20
|
+
try {
|
21
|
+
while (target != null && target !== win.top && !isPrebidWindow(target)) target = target.parent;
|
22
|
+
if (!isPrebidWindow(target)) target = win.parent;
|
23
|
+
} catch (e) {}
|
24
|
+
|
12
25
|
return function sendMessage(message, onResponse) {
|
13
26
|
if (prebidDomain == null) {
|
14
27
|
throw new Error('Missing pubUrl')
|
@@ -16,13 +29,13 @@ export function prebidMessenger(publisherURL, win = window) {
|
|
16
29
|
message = JSON.stringify(message);
|
17
30
|
let messagePort;
|
18
31
|
if (onResponse == null) {
|
19
|
-
|
32
|
+
target.postMessage(message, prebidDomain);
|
20
33
|
} else {
|
21
34
|
const channel = new MessageChannel();
|
22
35
|
messagePort = channel.port1;
|
23
36
|
messagePort.onmessage = onResponse;
|
24
37
|
win.addEventListener('message', windowListener);
|
25
|
-
|
38
|
+
target.postMessage(message, prebidDomain, [channel.port2]);
|
26
39
|
}
|
27
40
|
|
28
41
|
return function stopListening() {
|
@@ -41,3 +54,11 @@ export function prebidMessenger(publisherURL, win = window) {
|
|
41
54
|
|
42
55
|
}
|
43
56
|
}
|
57
|
+
|
58
|
+
export function renderEventMessage(adId, errorInfo) {
|
59
|
+
return Object.assign({
|
60
|
+
adId,
|
61
|
+
message: PREBID_EVENT,
|
62
|
+
event: errorInfo ? AD_RENDER_FAILED : AD_RENDER_SUCCEEDED,
|
63
|
+
}, errorInfo ? {info: errorInfo} : null)
|
64
|
+
}
|