diff --git a/.circleci/config.yml b/.circleci/config.yml
index 62c23390666..fbf7e77e10f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -33,7 +33,7 @@ jobs:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- - run: sudo npm install -g gulp
+ - run: sudo npm install -g gulp-cli
# Download and run BrowserStack local
- run:
name : Download BrowserStack Local binary and start it.
diff --git a/PREBID_VERSIONING_DEPRECATION.md b/PREBID_VERSIONING_DEPRECATION.md
new file mode 100644
index 00000000000..f006922259b
--- /dev/null
+++ b/PREBID_VERSIONING_DEPRECATION.md
@@ -0,0 +1,25 @@
+# Prebid versioning and deprecation policy
+
+## Goals
+Provide clear definitions and policy around versioning and breaking changes to APIs that are both publisher and demand partner facing.
+
+ - Limit the number of breaking changes.
+ - Ensure significant time for updates for breaking changes so that publisher or demand partners do not break.
+ - Provide a path to deprecation and reduce technical debt and increase security.
+ - Major versions should not be changed more than once per 30 days.
+
+## Versioning
+
+Follow semantic versioning so that all breaking changes occur within a major release. A breaking change includes both demand partner internal APIs* and publisher facing APIs (global APIs).
+
+*Demand partner APIs may be excluded from breaking change policy at the core teams discretion if the changes are made so to be transparent to the bidders (such as internal refactoring).
+
+## Deprecation process
+
+ - Open an issue with an "intent to implement" and "API impact" labels.
+ - Allow 2 weeks for discussion.
+ - Announce breaking change to the mailing list (TBD needs to be created).
+ - At least 2 core members needs to provide explicit approval for the deprecation.
+ - Open a PR against current master for console warning for possible breakage.
+ - Support the previous major version for a minimum of 30 days.
+ - Coordinate with the core team to ensure clean merging into feature branch if applicable (future major version branch).
diff --git a/PR_REVIEW.md b/PR_REVIEW.md
index 012a2d8b501..d5799472377 100644
--- a/PR_REVIEW.md
+++ b/PR_REVIEW.md
@@ -18,6 +18,7 @@ For modules and core platform updates, the initial reviewer should request an ad
- Once there is 2 `LGTM` on the PR, merge to master
- Ask the submitter to add a PR for documentation if applicable.
- Add a line into the [draft release](https://github.com/prebid/Prebid.js/releases) notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479)
+- Add the PR to the appropriate project board (I.E. 1.23.0 Release) for the week, [see](https://github.com/prebid/Prebid.js/projects)
### New Adapter or updates to adapter process
- Follow steps above for general review process. In addition, please verify the following:
@@ -39,9 +40,9 @@ For modules and core platform updates, the initial reviewer should request an ad
## Ticket Coordinator
Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. That person should:
-- Review issues and PRs at least once per weekday for new items.
+- Review issues and PRs at least once per weekday for new items. Encourage a 48 "SLA" on PRs/issues assigned. Aim for touchpoint once every 48/hours.
- For PRs: assign PRs to individuals on the PR review list. Try to be equitable -- not all PRs are created equally. Use the "Assigned" field and add the "Needs Review" label.
-- For Issues: try to address questions and troubleshooting requests on your own, assigning them to others as needed.
+- For Issues: try to address questions and troubleshooting requests on your own, assigning them to others as needed. Please add labels as appropriate (I.E. bug, question, backlog etc).
- Issues that are questions or troubleshooting requests may be closed if the originator doesn't respond within a week to requests for confirmation or details.
- Issues that are bug reports should be left open and assigned to someone in PR rotation to confirm or deny the bug status.
- It's polite to check with others before assigning them large tasks.
diff --git a/README.md b/README.md
index e43833fd4d2..a89f68b8abc 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,8 @@ Working examples can be found in [the developer docs](http://prebid.org/dev-docs
$ npm install
*Note:* You need to have `NodeJS` 4.x or greater installed.
+*Note:* Because we have transitioned to using gulp 4.0 - you need to have `gulp-cli` installed globally prior to running the general `npm install`. Run the following command to perform the install: `npm install gulp-cli -g`
+If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. This removal can be done with the command: `npm rm gulp -g`
@@ -168,7 +170,7 @@ Many SSPs, bidders, and publishers have contributed to this project. [60+ Bidder
For guidelines, see [Contributing](./CONTRIBUTING.md).
-Our PR review process can be found [here](https://github.com/prebid/Prebid.js/tree/master/pr_review.md).
+Our PR review process can be found [here](https://github.com/prebid/Prebid.js/tree/master/PR_REVIEW.md).
### Add a Bidder Adapter
diff --git a/build.sh b/build.sh
new file mode 100755
index 00000000000..a462c611e4a
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,7 @@
+rm -fr build/dist/
+rm -fr node_modules
+npm install
+gulp build --modules=modules.json
+mv build/dist/prebid.js build/dist/prebid.`date +"d%Y%m%d%s"`.js
+open build/dist/
+
diff --git a/gulpfile.js b/gulpfile.js
index 92dd2a7c1f1..ced29b266a7 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -5,18 +5,15 @@ var argv = require('yargs').argv;
var gulp = require('gulp');
var gutil = require('gulp-util');
var connect = require('gulp-connect');
-var path = require('path');
var webpack = require('webpack');
var webpackStream = require('webpack-stream');
var uglify = require('gulp-uglify');
-var clean = require('gulp-clean');
+var gulpClean = require('gulp-clean');
var KarmaServer = require('karma').Server;
var karmaConfMaker = require('./karma.conf.maker');
var opens = require('open');
var webpackConfig = require('./webpack.conf');
var helpers = require('./gulpHelpers');
-var del = require('del');
-var gulpDocumentation = require('gulp-documentation');
var concat = require('gulp-concat');
var header = require('gulp-header');
var footer = require('gulp-footer');
@@ -36,23 +33,131 @@ var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' *
var analyticsDirectory = '../analytics';
var port = 9999;
-// Tasks
-gulp.task('default', ['webpack']);
+// these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules
+var explicitModules = [
+ 'pre1api'
+];
-gulp.task('serve', ['build-bundle-dev', 'watch', 'test']);
+// all the following functions are task functions
+function bundleToStdout() {
+ nodeBundle().then(file => console.log(file));
+}
+bundleToStdout.displayName = 'bundle-to-stdout';
-gulp.task('serve-nw', ['lint', 'watch', 'e2etest']);
+function clean() {
+ return gulp.src(['build'], {
+ read: false,
+ allowEmpty: true
+ })
+ .pipe(gulpClean());
+}
-gulp.task('run-tests', ['lint', 'test-coverage']);
+function e2etestReport() {
+ var reportPort = 9010;
+ var targetDestinationDir = './e2etest-report';
+ helpers.createEnd2EndTestReport(targetDestinationDir);
+ connect.server({
+ port: reportPort,
+ root: './',
+ livereload: true
+ });
-gulp.task('build', ['build-bundle-prod']);
+ setTimeout(function() {
+ opens('http://localhost:' + reportPort + '/' + targetDestinationDir.slice(2) + '/results.html');
+ }, 5000);
+};
+e2etestReport.displayName = 'e2etest-report';
-gulp.task('clean', function () {
- return gulp.src(['build'], {
- read: false
- })
- .pipe(clean());
-});
+// Dependant task for building postbid. It escapes postbid-config file.
+function escapePostbidConfig() {
+ gulp.src('./integrationExamples/postbid/oas/postbid-config.js')
+ .pipe(jsEscape())
+ .pipe(gulp.dest('build/postbid/'));
+};
+escapePostbidConfig.displayName = 'escape-postbid-config';
+
+function lint() {
+ return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'])
+ .pipe(eslint())
+ .pipe(eslint.format('stylish'))
+ .pipe(eslint.failAfterError());
+};
+
+// View the code coverage report in the browser.
+function viewCoverage(done) {
+ var coveragePort = 1999;
+
+ connect.server({
+ port: coveragePort,
+ root: 'build/coverage/karma_html',
+ livereload: false
+ });
+ opens('http://localhost:' + coveragePort);
+ done();
+};
+viewCoverage.displayName = 'view-coverage';
+
+// Watch Task with Live Reload
+function watch(done) {
+ var mainWatcher = gulp.watch([
+ 'src/**/*.js',
+ 'modules/**/*.js',
+ 'test/spec/**/*.js',
+ '!test/spec/loaders/**/*.js'
+ ]);
+ var loaderWatcher = gulp.watch([
+ 'loaders/**/*.js',
+ 'test/spec/loaders/**/*.js'
+ ]);
+
+ connect.server({
+ https: argv.https,
+ port: port,
+ root: './',
+ livereload: true
+ });
+
+ mainWatcher.on('all', gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test)));
+ loaderWatcher.on('all', gulp.series(lint));
+ done();
+};
+
+function makeDevpackPkg() {
+ var cloned = _.cloneDeep(webpackConfig);
+ cloned.devtool = 'source-map';
+ var externalModules = helpers.getArgModules();
+
+ const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
+ const moduleSources = helpers.getModulePaths(externalModules);
+
+ return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
+ .pipe(helpers.nameModules(externalModules))
+ .pipe(webpackStream(cloned, webpack))
+ .pipe(replace('$prebid.version$', prebid.version))
+ .pipe(gulp.dest('build/dev'))
+ .pipe(connect.reload());
+}
+
+function makeWebpackPkg() {
+ var cloned = _.cloneDeep(webpackConfig);
+
+ delete cloned.devtool;
+
+ var externalModules = helpers.getArgModules();
+
+ const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
+ const moduleSources = helpers.getModulePaths(externalModules);
+
+ return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
+ .pipe(helpers.nameModules(externalModules))
+ .pipe(webpackStream(cloned, webpack))
+ .pipe(replace('$prebid.version$', prebid.version))
+ .pipe(uglify())
+ .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid })))
+ .pipe(optimizejs())
+ .pipe(gulp.dest('build/dist'))
+ .pipe(connect.reload());
+}
function gulpBundle(dev) {
return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist')));
@@ -71,17 +176,12 @@ function nodeBundle(modules) {
});
}
-// these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules
-var explicitModules = [
- 'pre1api'
-];
-
function bundle(dev, moduleArr) {
- var modules = moduleArr || helpers.getArgModules(),
- allModules = helpers.getModuleNames(modules);
+ var modules = moduleArr || helpers.getArgModules();
+ var allModules = helpers.getModuleNames(modules);
if (modules.length === 0) {
- modules = allModules.filter(module => !explicitModules.includes(module));
+ modules = allModules.filter(module => explicitModules.indexOf(module) === -1);
} else {
var diff = _.difference(modules, allModules);
if (diff.length !== 0) {
@@ -125,7 +225,8 @@ function newKarmaCallback(done) {
done(new Error('Karma tests failed with exit code ' + exitCode));
} else {
if (argv.browserstack) {
- process.exit(0);
+ // process.exit(0);
+ done(); // test this with travis (or circleci)
} else {
done();
}
@@ -133,51 +234,6 @@ function newKarmaCallback(done) {
}
}
-gulp.task('build-bundle-dev', ['devpack'], gulpBundle.bind(null, true));
-gulp.task('build-bundle-prod', ['webpack'], gulpBundle.bind(null, false));
-gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step
-
-gulp.task('bundle-to-stdout', function() {
- nodeBundle().then(file => console.log(file));
-});
-
-gulp.task('devpack', ['clean'], function () {
- var cloned = _.cloneDeep(webpackConfig);
- cloned.devtool = 'source-map';
- var externalModules = helpers.getArgModules();
-
- const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
- const moduleSources = helpers.getModulePaths(externalModules);
-
- return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
- .pipe(helpers.nameModules(externalModules))
- .pipe(webpackStream(cloned, webpack))
- .pipe(replace('$prebid.version$', prebid.version))
- .pipe(gulp.dest('build/dev'))
- .pipe(connect.reload());
-});
-
-gulp.task('webpack', ['clean'], function () {
- var cloned = _.cloneDeep(webpackConfig);
-
- delete cloned.devtool;
-
- var externalModules = helpers.getArgModules();
-
- const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory);
- const moduleSources = helpers.getModulePaths(externalModules);
-
- return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
- .pipe(helpers.nameModules(externalModules))
- .pipe(webpackStream(cloned, webpack))
- .pipe(replace('$prebid.version$', prebid.version))
- .pipe(uglify())
- .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid })))
- .pipe(optimizejs())
- .pipe(gulp.dest('build/dist'))
- .pipe(connect.reload());
-});
-
// Run the unit tests.
//
// By default, this runs in headless chrome.
@@ -186,41 +242,33 @@ gulp.task('webpack', ['clean'], function () {
// If --file "" is given, the task will only run tests in the specified file.
// If --browserstack is given, it will run the full suite of currently supported browsers.
// If --browsers is given, browsers can be chosen explicitly. e.g. --browsers=chrome,firefox,ie9
-gulp.task('test', ['clean', 'lint'], function (done) {
- var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file);
+// If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`)
+function test(done) {
+ if (argv.notest) {
+ done();
+ } else {
+ var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file);
- var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase);
- if (browserOverride.length > 0) {
- karmaConf.browsers = browserOverride;
- }
+ var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase);
+ if (browserOverride.length > 0) {
+ karmaConf.browsers = browserOverride;
+ }
- new KarmaServer(karmaConf, newKarmaCallback(done)).start();
-});
+ new KarmaServer(karmaConf, newKarmaCallback(done)).start();
+ }
+}
// If --file "" is given, the task will only run tests in the specified file.
-gulp.task('test-coverage', ['clean'], function(done) {
+function testCoverage(done) {
new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start();
-});
-
-// View the code coverage report in the browser.
-gulp.task('view-coverage', function (done) {
- var coveragePort = 1999;
-
- connect.server({
- port: coveragePort,
- root: 'build/coverage/karma_html',
- livereload: false
- });
- opens('http://localhost:' + coveragePort);
- done();
-});
+}
-gulp.task('coveralls', ['test-coverage'], function() { // 2nd arg is a dependency: 'test' must be finished
+function coveralls() { // 2nd arg is a dependency: 'test' must be finished
// first send results of istanbul's test coverage to coveralls.io.
return gulp.src('gulpfile.js', { read: false }) // You have to give it a file, but you don't
// have to read it.
.pipe(shell('cat build/coverage/lcov.info | node_modules/coveralls/bin/coveralls.js'));
-});
+}
// Watch Task with Live Reload
gulp.task('watch', function () {
@@ -242,27 +290,7 @@ gulp.task('watch', function () {
});
});
-gulp.task('lint', () => {
- return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'])
- .pipe(eslint())
- .pipe(eslint.format('stylish'))
- .pipe(eslint.failAfterError());
-});
-
-gulp.task('clean-docs', function () {
- del(['docs']);
-});
-
-gulp.task('docs', ['clean-docs'], function () {
- return gulp.src('src/prebid.js')
- .pipe(gulpDocumentation('md'))
- .on('error', function (err) {
- gutil.log('`gulp-documentation` failed:', err.message);
- })
- .pipe(gulp.dest('docs'));
-});
-
-gulp.task('e2etest', ['devpack', 'webpack'], function() {
+function e2eTest() {
var cmdQueue = [];
if (argv.browserstack) {
var browsers = require('./browsers.json');
@@ -289,38 +317,50 @@ gulp.task('e2etest', ['devpack', 'webpack'], function() {
return gulp.src('')
.pipe(shell(cmdQueue.join(';')));
-});
-
-gulp.task('e2etest-report', function() {
- var reportPort = 9010;
- var targetDestinationDir = './e2etest-report';
- helpers.createEnd2EndTestReport(targetDestinationDir);
- connect.server({
- port: reportPort,
- root: './',
- livereload: true
- });
-
- setTimeout(function() {
- opens('http://localhost:' + reportPort + '/' + targetDestinationDir.slice(2) + '/results.html');
- }, 5000);
-});
+}
// This task creates postbid.js. Postbid setup is different from prebid.js
// More info can be found here http://prebid.org/overview/what-is-post-bid.html
-gulp.task('build-postbid', ['escape-postbid-config'], function() {
+
+function buildPostbid() {
var fileContent = fs.readFileSync('./build/postbid/postbid-config.js', 'utf8');
return gulp.src('./integrationExamples/postbid/oas/postbid.js')
.pipe(replace('\[%%postbid%%\]', fileContent))
.pipe(gulp.dest('build/postbid/'));
-});
+}
-// Dependant task for building postbid. It escapes postbid-config file.
-gulp.task('escape-postbid-config', function() {
- gulp.src('./integrationExamples/postbid/oas/postbid-config.js')
- .pipe(jsEscape())
- .pipe(gulp.dest('build/postbid/'));
-});
+// support tasks
+gulp.task(lint);
+gulp.task(watch);
+
+gulp.task(clean);
+
+gulp.task(escapePostbidConfig);
+
+gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, true)));
+gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, gulpBundle.bind(null, false)));
+
+// public tasks (dependencies are needed for each task since they can be ran on their own)
+gulp.task('test', gulp.series(clean, lint, test));
+
+gulp.task('test-coverage', gulp.series(clean, testCoverage));
+gulp.task(viewCoverage);
+
+gulp.task('coveralls', gulp.series('test-coverage', coveralls));
+
+gulp.task('build', gulp.series(clean, 'build-bundle-prod'));
+gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid));
+
+gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test)));
+gulp.task('default', gulp.series(clean, makeWebpackPkg));
+
+gulp.task(e2etestReport);
+gulp.task('e2etest', gulp.series(clean, gulp.parallel(makeDevpackPkg, makeWebpackPkg), e2eTest));
+
+// other tasks
+gulp.task(bundleToStdout);
+gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step
+gulp.task('serve-nw', gulp.parallel(lint, watch, 'e2etest'));
module.exports = nodeBundle;
diff --git a/modules.json b/modules.json
new file mode 100644
index 00000000000..dd6401028cd
--- /dev/null
+++ b/modules.json
@@ -0,0 +1,13 @@
+[
+ "consentManagement",
+ "adformBidAdapter",
+ "appnexusBidAdapter",
+ "criteoBidAdapter",
+ "openxBidAdapter",
+ "pubmaticBidAdapter",
+ "rubiconBidAdapter",
+ "widespaceBidAdapter",
+ "ixBidAdapter",
+ "improvedigitalBidAdapter",
+ "hpcAnalyticsAdapter"
+]
diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js
index 6b41c652152..497cf9f7110 100644
--- a/modules/33acrossBidAdapter.js
+++ b/modules/33acrossBidAdapter.js
@@ -1,12 +1,16 @@
-import { uniques } from 'src/utils';
+import * as utils from 'src/utils';
+
const { registerBidder } = require('../src/adapters/bidderFactory');
const { config } = require('../src/config');
+
const BIDDER_CODE = '33across';
const END_POINT = 'https://ssc.33across.com/api/v1/hb';
const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch&rt=html';
const adapterState = {};
+const NON_MEASURABLE = 'nm';
+
// All this assumes that only one bid is ever returned by ttx
function _createBidResponse(response) {
return {
@@ -23,11 +27,30 @@ function _createBidResponse(response) {
}
}
+function _isViewabilityMeasurable() {
+ return !_isIframe();
+}
+
+function _getViewability(element, topWin, { w, h } = {}) {
+ return utils.getWindowTop().document.visibilityState === 'visible'
+ ? _getPercentInView(element, topWin, { w, h })
+ : 0;
+}
+
// Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request
// NOTE: At this point, TTX only accepts request for a single impression
function _createServerRequest(bidRequest, gdprConsent) {
const ttxRequest = {};
const params = bidRequest.params;
+ const element = document.getElementById(bidRequest.adUnitCode);
+ const sizes = _transformSizes(bidRequest.sizes);
+ const minSize = _getMinSize(sizes);
+
+ const viewabilityAmount = _isViewabilityMeasurable()
+ ? _getViewability(element, utils.getWindowTop(), minSize)
+ : NON_MEASURABLE;
+
+ const contributeViewability = ViewabilityContributor(viewabilityAmount);
/*
* Infer data for the request payload
@@ -35,14 +58,14 @@ function _createServerRequest(bidRequest, gdprConsent) {
ttxRequest.imp = [];
ttxRequest.imp[0] = {
banner: {
- format: bidRequest.sizes.map(_getFormatSize)
+ format: sizes.map(size => Object.assign(size, {ext: {}}))
},
ext: {
ttx: {
prod: params.productId
}
}
- }
+ };
ttxRequest.site = { id: params.siteId };
// Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and
@@ -54,12 +77,12 @@ function _createServerRequest(bidRequest, gdprConsent) {
ext: {
consent: gdprConsent.consentString
}
- }
+ };
ttxRequest.regs = {
ext: {
gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0
}
- }
+ };
// Finally, set the openRTB 'test' param if this is to be a test bid
if (params.test === 1) {
@@ -81,7 +104,7 @@ function _createServerRequest(bidRequest, gdprConsent) {
return {
'method': 'POST',
'url': url,
- 'data': JSON.stringify(ttxRequest),
+ 'data': JSON.stringify(contributeViewability(ttxRequest)),
'options': options
}
}
@@ -97,11 +120,118 @@ function _createSync(siteId) {
}
}
-function _getFormatSize(sizeArr) {
+function _getSize(size) {
return {
- w: sizeArr[0],
- h: sizeArr[1],
- ext: {}
+ w: parseInt(size[0], 10),
+ h: parseInt(size[1], 10)
+ }
+}
+
+function _getMinSize(sizes) {
+ return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min);
+}
+
+function _getBoundingBox(element, { w, h } = {}) {
+ let { width, height, left, top, right, bottom } = element.getBoundingClientRect();
+
+ if ((width === 0 || height === 0) && w && h) {
+ width = w;
+ height = h;
+ right = left + w;
+ bottom = top + h;
+ }
+
+ return { width, height, left, top, right, bottom };
+}
+
+function _transformSizes(sizes) {
+ if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) {
+ return [_getSize(sizes)];
+ }
+
+ return sizes.map(_getSize);
+}
+
+function _getIntersectionOfRects(rects) {
+ const bbox = {
+ left: rects[0].left,
+ right: rects[0].right,
+ top: rects[0].top,
+ bottom: rects[0].bottom
+ };
+
+ for (let i = 1; i < rects.length; ++i) {
+ bbox.left = Math.max(bbox.left, rects[i].left);
+ bbox.right = Math.min(bbox.right, rects[i].right);
+
+ if (bbox.left >= bbox.right) {
+ return null;
+ }
+
+ bbox.top = Math.max(bbox.top, rects[i].top);
+ bbox.bottom = Math.min(bbox.bottom, rects[i].bottom);
+
+ if (bbox.top >= bbox.bottom) {
+ return null;
+ }
+ }
+
+ bbox.width = bbox.right - bbox.left;
+ bbox.height = bbox.bottom - bbox.top;
+
+ return bbox;
+}
+
+function _getPercentInView(element, topWin, { w, h } = {}) {
+ const elementBoundingBox = _getBoundingBox(element, { w, h });
+
+ // Obtain the intersection of the element and the viewport
+ const elementInViewBoundingBox = _getIntersectionOfRects([ {
+ left: 0,
+ top: 0,
+ right: topWin.innerWidth,
+ bottom: topWin.innerHeight
+ }, elementBoundingBox ]);
+
+ let elementInViewArea, elementTotalArea;
+
+ if (elementInViewBoundingBox !== null) {
+ // Some or all of the element is in view
+ elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height;
+ elementTotalArea = elementBoundingBox.width * elementBoundingBox.height;
+
+ return ((elementInViewArea / elementTotalArea) * 100);
+ }
+
+ // No overlap between element and the viewport; therefore, the element
+ // lies completely out of view
+ return 0;
+}
+
+/**
+ * Viewability contribution to request..
+ */
+function ViewabilityContributor(viewabilityAmount) {
+ function contributeViewability(ttxRequest) {
+ const req = Object.assign({}, ttxRequest);
+ const imp = req.imp = req.imp.map(impItem => Object.assign({}, impItem));
+ const banner = imp[0].banner = Object.assign({}, imp[0].banner);
+ const ext = banner.ext = Object.assign({}, banner.ext);
+ const ttx = ext.ttx = Object.assign({}, ext.ttx);
+
+ ttx.viewability = { amount: isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount) };
+
+ return req;
+ }
+
+ return contributeViewability;
+}
+
+function _isIframe() {
+ try {
+ return utils.getWindowSelf() !== utils.getWindowTop();
+ } catch (e) {
+ return true;
}
}
@@ -122,9 +252,9 @@ function isBidRequestValid(bid) {
// - the server, at this point, also doesn't need the consent string to handle gdpr compliance. So passing
// value whether set or not, for the sake of future dev.
function buildRequests(bidRequests, bidderRequest) {
- const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent)
+ const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent);
- adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques);
+ adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques);
return bidRequests.map((req) => {
return _createServerRequest(req, gdprConsent);
@@ -153,14 +283,15 @@ function getUserSyncs(syncOptions, responses, gdprConsent) {
}
}
-const spec = {
+export const spec = {
+ NON_MEASURABLE,
+
code: BIDDER_CODE,
+
isBidRequestValid,
buildRequests,
interpretResponse,
- getUserSyncs
-}
+ getUserSyncs,
+};
registerBidder(spec);
-
-module.exports = spec;
diff --git a/modules/aardvarkBidAdapter.js b/modules/aardvarkBidAdapter.js
index 6a4c8b99572..3569999b998 100644
--- a/modules/aardvarkBidAdapter.js
+++ b/modules/aardvarkBidAdapter.js
@@ -28,9 +28,12 @@ export const spec = {
var referer = utils.getTopWindowUrl();
var pageCategories = [];
- if (window.top.rtkcategories && Array.isArray(window.top.rtkcategories)) {
- pageCategories = window.top.rtkcategories;
- }
+ // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch.
+ try {
+ if (window.top.rtkcategories && Array.isArray(window.top.rtkcategories)) {
+ pageCategories = window.top.rtkcategories;
+ }
+ } catch (e) {}
utils._each(validBidRequests, function(b) {
var rMap = requestsMap[b.params.ai];
diff --git a/modules/adoceanBidAdapter.js b/modules/adoceanBidAdapter.js
index 21e13e77a0a..e84fd04a35e 100644
--- a/modules/adoceanBidAdapter.js
+++ b/modules/adoceanBidAdapter.js
@@ -16,7 +16,7 @@ function buildEndpointUrl(emiter, payload) {
}
function buildRequest(masterBidRequests, masterId, gdprConsent) {
- const firstBid = masterBidRequests[0];
+ let emiter;
const payload = {
id: masterId,
};
@@ -27,13 +27,16 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) {
const bidIdMap = {};
- utils._each(masterBidRequests, function(v) {
- bidIdMap[v.params.slaveId] = v.bidId;
+ utils._each(masterBidRequests, function(bid, slaveId) {
+ if (!emiter) {
+ emiter = bid.params.emiter;
+ }
+ bidIdMap[slaveId] = bid.bidId;
});
return {
method: 'GET',
- url: buildEndpointUrl(firstBid.params.emiter, payload),
+ url: buildEndpointUrl(emiter, payload),
data: {},
bidIdMap: bidIdMap
};
@@ -41,8 +44,16 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) {
function assignToMaster(bidRequest, bidRequestsByMaster) {
const masterId = bidRequest.params.masterId;
- bidRequestsByMaster[masterId] = bidRequestsByMaster[masterId] || [];
- bidRequestsByMaster[masterId].push(bidRequest);
+ const slaveId = bidRequest.params.slaveId;
+ const masterBidRequests = bidRequestsByMaster[masterId] = bidRequestsByMaster[masterId] || [{}];
+ let i = 0;
+ while (masterBidRequests[i] && masterBidRequests[i][slaveId]) {
+ i++;
+ }
+ if (!masterBidRequests[i]) {
+ masterBidRequests[i] = {};
+ }
+ masterBidRequests[i][slaveId] = bidRequest;
}
function interpretResponse(placementResponse, bidRequest, bids) {
@@ -83,8 +94,11 @@ export const spec = {
utils._each(validBidRequests, function(bidRequest) {
assignToMaster(bidRequest, bidRequestsByMaster);
});
- requests = utils._map(bidRequestsByMaster, function(requests, masterId) {
- return buildRequest(requests, masterId, bidderRequest.gdprConsent);
+
+ utils._each(bidRequestsByMaster, function(masterRequests, masterId) {
+ utils._each(masterRequests, function(instanceRequests) {
+ requests.push(buildRequest(instanceRequests, masterId, bidderRequest.gdprConsent));
+ });
});
return requests;
diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js
index 28e8cb0b46e..b97252bf9b3 100644
--- a/modules/aolBidAdapter.js
+++ b/modules/aolBidAdapter.js
@@ -1,7 +1,5 @@
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
-import { config } from 'src/config';
-import { EVENTS } from 'src/constants.json';
import { BANNER } from 'src/mediaTypes';
const AOL_BIDDERS_CODES = {
@@ -43,31 +41,11 @@ const NEXAGE_SERVER = 'hb.nexage.com';
const ONE_DISPLAY_TTL = 60;
const ONE_MOBILE_TTL = 3600;
-$$PREBID_GLOBAL$$.aolGlobals = {
- pixelsDropped: false
-};
-
const NUMERIC_VALUES = {
TRUE: 1,
FALSE: 0
};
-let showCpmAdjustmentWarning = (function() {
- let showCpmWarning = true;
-
- return function() {
- let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings;
- if (showCpmWarning && bidderSettings && bidderSettings.aol &&
- typeof bidderSettings.aol.bidCpmAdjustment === 'function') {
- utils.logWarn(
- 'bidCpmAdjustment is active for the AOL adapter. ' +
- 'As of Prebid 0.14, AOL can bid in net – please contact your accounts team to enable.'
- );
- showCpmWarning = false; // warning is shown at most once
- }
- };
-})();
-
function template(strings, ...keys) {
return function(...values) {
let dict = values[values.length - 1] || {};
@@ -80,32 +58,6 @@ function template(strings, ...keys) {
};
}
-function parsePixelItems(pixels) {
- let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi;
- let tagNameRegExp = /\w*(?=\s)/;
- let srcRegExp = /src=("|')(.*?)\1/;
- let pixelsItems = [];
-
- if (pixels) {
- let matchedItems = pixels.match(itemsRegExp);
- if (matchedItems) {
- matchedItems.forEach(item => {
- let tagName = item.match(tagNameRegExp)[0];
- let url = item.match(srcRegExp)[2];
-
- if (tagName && tagName) {
- pixelsItems.push({
- type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE,
- url: url
- });
- }
- });
- }
- }
-
- return pixelsItems;
-}
-
function _isMarketplaceBidder(bidder) {
return bidder === AOL_BIDDERS_CODES.AOL || bidder === AOL_BIDDERS_CODES.ONEDISPLAY;
}
@@ -164,8 +116,6 @@ export const spec = {
});
},
interpretResponse({body}, bidRequest) {
- showCpmAdjustmentWarning();
-
if (!body) {
utils.logError('Empty bid response', bidRequest.bidderCode, body);
} else {
@@ -176,15 +126,11 @@ export const spec = {
}
}
},
- getUserSyncs(options, bidResponses) {
- let bidResponse = bidResponses[0];
+ getUserSyncs(options, serverResponses) {
+ const bidResponse = !utils.isEmpty(serverResponses) && serverResponses[0].body;
- if (config.getConfig('aol.userSyncOn') === EVENTS.BID_RESPONSE) {
- if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse && bidResponse.ext && bidResponse.ext.pixels) {
- $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true;
-
- return parsePixelItems(bidResponse.ext.pixels);
- }
+ if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) {
+ return this.parsePixelItems(bidResponse.ext.pixels);
}
return [];
@@ -357,6 +303,31 @@ export const spec = {
return params;
},
+ parsePixelItems(pixels) {
+ let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi;
+ let tagNameRegExp = /\w*(?=\s)/;
+ let srcRegExp = /src=("|')(.*?)\1/;
+ let pixelsItems = [];
+
+ if (pixels) {
+ let matchedItems = pixels.match(itemsRegExp);
+ if (matchedItems) {
+ matchedItems.forEach(item => {
+ let tagName = item.match(tagNameRegExp)[0];
+ let url = item.match(srcRegExp)[2];
+
+ if (tagName && tagName) {
+ pixelsItems.push({
+ type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE,
+ url: url
+ });
+ }
+ });
+ }
+ }
+
+ return pixelsItems;
+ },
_parseBidResponse(response, bidRequest) {
let bidData;
@@ -380,38 +351,20 @@ export const spec = {
}
}
- let bidResponse = {
+ return {
bidderCode: bidRequest.bidderCode,
requestId: bidRequest.bidId,
ad: bidData.adm,
cpm: cpm,
width: bidData.w,
height: bidData.h,
- creativeId: bidData.crid,
+ creativeId: bidData.crid || 0,
pubapiId: response.id,
- currency: response.cur,
+ currency: response.cur || 'USD',
dealId: bidData.dealid,
netRevenue: true,
ttl: bidRequest.ttl
};
-
- if (response.ext && response.ext.pixels) {
- if (config.getConfig('aol.userSyncOn') !== EVENTS.BID_RESPONSE) {
- bidResponse.ad += this.formatPixels(response.ext.pixels);
- }
- }
-
- return bidResponse;
- },
- formatPixels(pixels) {
- let formattedPixels = pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, '');
-
- return '';
},
isOneMobileBidder: _isOneMobileBidder,
isSecureProtocol() {
diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js
index 7dac4b8b182..aaec207dc1e 100644
--- a/modules/appnexusBidAdapter.js
+++ b/modules/appnexusBidAdapter.js
@@ -107,6 +107,16 @@ export const spec = {
};
}
+ if (bidderRequest && bidderRequest.refererInfo) {
+ let refererinfo = {
+ rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer),
+ rd_top: bidderRequest.refererInfo.reachedTop,
+ rd_ifs: bidderRequest.refererInfo.numIframes,
+ rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',')
+ }
+ payload.referrer_detection = refererinfo;
+ }
+
const payloadString = JSON.stringify(payload);
return {
method: 'POST',
diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js
index 544670863b8..6733b6ec007 100644
--- a/modules/audienceNetworkBidAdapter.js
+++ b/modules/audienceNetworkBidAdapter.js
@@ -3,7 +3,7 @@
*/
import { registerBidder } from 'src/adapters/bidderFactory';
import { formatQS } from 'src/url';
-import { generateUUID, getTopWindowUrl, isSafariBrowser, convertTypes } from 'src/utils';
+import { generateUUID, getTopWindowUrl, convertTypes } from 'src/utils';
import findIndex from 'core-js/library/fn/array/find-index';
import includes from 'core-js/library/fn/array/includes';
@@ -18,7 +18,7 @@ const ttl = 600;
const videoTtl = 3600;
const platver = '$prebid.version$';
const platform = '241394079772386';
-const adapterver = '1.0.1';
+const adapterver = '1.1.0';
/**
* Does this bid request contain valid parameters?
@@ -73,6 +73,22 @@ const isValidSizeAndFormat = (size, format) =>
isValidNonSizedFormat(format) ||
isValidSize(flattenSize(size));
+/**
+ * Find a preferred entry, if any, from an array of valid sizes.
+ * @param {Array} acc
+ * @param {String} cur
+ */
+const sortByPreferredSize = (acc, cur) =>
+ (cur === '300x250') ? [cur, ...acc] : [...acc, cur];
+
+/**
+ * Map any deprecated size/formats to new values.
+ * @param {String} size
+ * @param {String} format
+ */
+const mapDeprecatedSizeAndFormat = (size, format) =>
+ isFullWidth(format) ? ['300x250', null] : [size, format];
+
/**
* Is this a video format?
* @param {String} format
@@ -142,9 +158,9 @@ const getTopWindowUrlEncoded = () => encodeURIComponent(getTopWindowUrl());
* @param {Object} bids[].params
* @param {String} bids[].params.placementId - Audience Network placement identifier
* @param {String} bids[].params.platform - Audience Network platform identifier (optional)
- * @param {String} bids[].params.format - Optional format, one of 'video', 'native' or 'fullwidth' if set
+ * @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set
* @param {Array} bids[].sizes - list of desired advert sizes
- * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50]: first matched size is used
+ * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50]
* @returns {Array