diff --git a/.babelrc.js b/.babelrc.js
index 74d26caf0fd..43303f59a8b 100644
--- a/.babelrc.js
+++ b/.babelrc.js
@@ -14,16 +14,7 @@ module.exports = {
[
useLocal('@babel/preset-env'),
{
- "targets": {
- "browsers": [
- "chrome >= 75",
- "safari >=10",
- "edge >= 70",
- "ff >= 70",
- "ie >= 11",
- "ios >= 11"
- ]
- }
+ "useBuiltIns": "entry"
}
]
],
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ea5fb916a91..404026d9446 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -52,7 +52,7 @@ aliases:
- &unit_test_steps
- checkout
- restore_cache: *restore_dep_cache
- - run: npm install
+ - run: npm ci
- save_cache: *save_dep_cache
- run: *install
- run: *setup_browserstack
diff --git a/README.md b/README.md
index be16e9e3547..bc0e64afa06 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,8 @@ module.exports = {
loader: 'babel-loader',
// presets and plugins for Prebid.js must be manually specified separate from your other babel rule.
// this can be accomplished by requiring prebid's .babelrc.js file (requires Babel 7 and Node v8.9.0+)
+ // as of Prebid 6, babelrc.js only targets modern browsers. One can change the targets and build for
+ // older browsers if they prefer, but integration tests on ie11 were removed in Prebid.js 6.0
options: require('prebid.js/.babelrc.js')
}
}
@@ -272,7 +274,7 @@ As you make code changes, the bundles will be rebuilt and the page reloaded auto
## Contribute
-Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/src/adapters) are supported by Prebid.js.
+Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/modules) are supported by Prebid.js.
For guidelines, see [Contributing](./CONTRIBUTING.md).
@@ -314,7 +316,7 @@ For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http://
### Supported Browsers
-Prebid.js is supported on IE11 and modern browsers.
+Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11.
### Governance
Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md).
diff --git a/browsers.json b/browsers.json
index 4f2ea456f68..bd6bd5772d6 100644
--- a/browsers.json
+++ b/browsers.json
@@ -1,74 +1,51 @@
{
- "bs_edge_17_windows_10": {
+ "bs_edge_latest_windows_10": {
"base": "BrowserStack",
"os_version": "10",
"browser": "edge",
- "browser_version": "17.0",
+ "browser_version": "latest",
"device": null,
"os": "Windows"
},
- "bs_edge_90_windows_10": {
- "base": "BrowserStack",
- "os_version": "10",
- "browser": "edge",
- "browser_version": "90.0",
- "device": null,
- "os": "Windows"
- },
- "bs_ie_11_windows_10": {
- "base": "BrowserStack",
- "os_version": "10",
- "browser": "ie",
- "browser_version": "11.0",
- "device": null,
- "os": "Windows"
- },
- "bs_chrome_90_windows_10": {
+ "bs_chrome_latest_windows_10": {
"base": "BrowserStack",
"os_version": "10",
"browser": "chrome",
- "browser_version": "90.0",
+ "browser_version": "latest",
"device": null,
"os": "Windows"
},
- "bs_chrome_79_windows_10": {
+ "bs_chrome_87_windows_10": {
"base": "BrowserStack",
"os_version": "10",
"browser": "chrome",
- "browser_version": "79.0",
- "device": null,
- "os": "Windows"
- },
- "bs_firefox_88_windows_10": {
- "base": "BrowserStack",
- "os_version": "10",
- "browser": "firefox",
- "browser_version": "88.0",
+ "browser_version": "87.0",
"device": null,
"os": "Windows"
},
- "bs_firefox_72_windows_10": {
+ "bs_firefox_latest_windows_10": {
"base": "BrowserStack",
"os_version": "10",
"browser": "firefox",
- "browser_version": "72.0",
+ "browser_version": "latest",
"device": null,
"os": "Windows"
},
- "bs_safari_14_mac_bigsur": {
+ "bs_safari_latest_mac_bigsur": {
"base": "BrowserStack",
"os_version": "Big Sur",
"browser": "safari",
- "browser_version": "14.0",
+ "browser_version": "latest",
"device": null,
"os": "OS X"
},
- "bs_safari_12_mac_mojave": {
+ "bs_safari_15_catalina": {
"base": "BrowserStack",
- "os_version": "Mojave",
+ "os_version": "Catalina",
"browser": "safari",
- "browser_version": "12.0",
+ "browser_version": "13.1",
"device": null,
"os": "OS X"
}
+
}
diff --git a/gulpfile.js b/gulpfile.js
index 8609177a8b9..6ecfee1b672 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -76,6 +76,7 @@ function lint(done) {
'modules/**/*.js',
'test/**/*.js',
'plugins/**/*.js',
+ '!plugins/**/node_modules/**',
'./*.js'
], { base: './' })
.pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true })))
@@ -114,38 +115,6 @@ function viewReview(done) {
viewReview.displayName = 'view-review';
-// 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,
- host: FAKE_SERVER_HOST,
- root: './',
- livereload: true
- });
-
- mainWatcher.on('all', gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test)));
- loaderWatcher.on('all', gulp.series(lint));
- done();
-};
-
-function makeModuleList(modules) {
- return modules.map(module => {
- return '"' + module + '"'
- });
-}
-
function makeDevpackPkg() {
var cloned = _.cloneDeep(webpackConfig);
cloned.devtool = 'source-map';
@@ -157,7 +126,6 @@ function makeDevpackPkg() {
return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
.pipe(helpers.nameModules(externalModules))
.pipe(webpackStream(cloned, webpack))
- .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules)))
.pipe(gulp.dest('build/dev'))
.pipe(connect.reload());
}
@@ -175,7 +143,6 @@ function makeWebpackPkg() {
.pipe(helpers.nameModules(externalModules))
.pipe(webpackStream(cloned, webpack))
.pipe(terser())
- .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules)))
.pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid })))
.pipe(gulp.dest('build/dist'));
}
@@ -254,60 +221,68 @@ function bundle(dev, moduleArr) {
// If --browsers is given, browsers can be chosen explicitly. e.g. --browsers=chrome,firefox,ie9
// 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 if (argv.e2e) {
- let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio');
- let wdioConf = path.join(__dirname, 'wdio.conf.js');
- let wdioOpts;
-
- if (argv.file) {
- wdioOpts = [
- wdioConf,
- `--spec`,
- `${argv.file}`
- ]
- } else {
- wdioOpts = [
- wdioConf
- ];
- }
+function testTaskMaker(options = {}) {
+ ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => {
+ options[opt] = options[opt] || argv[opt];
+ })
- // run fake-server
- const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]);
- fakeServer.stdout.on('data', (data) => {
- console.log(`stdout: ${data}`);
- });
- fakeServer.stderr.on('data', (data) => {
- console.log(`stderr: ${data}`);
- });
+ return function test(done) {
+ if (options.notest) {
+ done();
+ } else if (options.e2e) {
+ let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio');
+ let wdioConf = path.join(__dirname, 'wdio.conf.js');
+ let wdioOpts;
+
+ if (options.file) {
+ wdioOpts = [
+ wdioConf,
+ `--spec`,
+ `${options.file}`
+ ]
+ } else {
+ wdioOpts = [
+ wdioConf
+ ];
+ }
- execa(wdioCmd, wdioOpts, { stdio: 'inherit' })
- .then(stdout => {
- // kill fake server
- fakeServer.kill('SIGINT');
- done();
- process.exit(0);
- })
- .catch(err => {
- // kill fake server
- fakeServer.kill('SIGINT');
- done(new Error(`Tests failed with error: ${err}`));
- process.exit(1);
+ // run fake-server
+ const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]);
+ fakeServer.stdout.on('data', (data) => {
+ console.log(`stdout: ${data}`);
+ });
+ fakeServer.stderr.on('data', (data) => {
+ console.log(`stderr: ${data}`);
});
- } else {
- var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file);
- var browserOverride = helpers.parseBrowserArgs(argv);
- if (browserOverride.length > 0) {
- karmaConf.browsers = browserOverride;
- }
+ execa(wdioCmd, wdioOpts, { stdio: 'inherit' })
+ .then(stdout => {
+ // kill fake server
+ fakeServer.kill('SIGINT');
+ done();
+ process.exit(0);
+ })
+ .catch(err => {
+ // kill fake server
+ fakeServer.kill('SIGINT');
+ done(new Error(`Tests failed with error: ${err}`));
+ process.exit(1);
+ });
+ } else {
+ var karmaConf = karmaConfMaker(false, options.browserstack, options.watch, options.file);
- new KarmaServer(karmaConf, newKarmaCallback(done)).start();
+ var browserOverride = helpers.parseBrowserArgs(argv);
+ if (browserOverride.length > 0) {
+ karmaConf.browsers = browserOverride;
+ }
+
+ new KarmaServer(karmaConf, newKarmaCallback(done)).start();
+ }
}
}
+const test = testTaskMaker();
+
function newKarmaCallback(done) {
return function (exitCode) {
if (exitCode) {
@@ -384,6 +359,35 @@ function startFakeServer() {
});
}
+// Watch Task with Live Reload
+function watchTaskMaker(options = {}) {
+ if (options.livereload == null) {
+ options.livereload = true;
+ }
+ options.alsoWatch = options.alsoWatch || [];
+
+ return function watch(done) {
+ var mainWatcher = gulp.watch([
+ 'src/**/*.js',
+ 'modules/**/*.js',
+ ].concat(options.alsoWatch));
+
+ connect.server({
+ https: argv.https,
+ port: port,
+ host: FAKE_SERVER_HOST,
+ root: './',
+ livereload: options.livereload
+ });
+
+ mainWatcher.on('all', options.task());
+ done();
+ }
+}
+
+const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))});
+const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')});
+
// support tasks
gulp.task(lint);
gulp.task(watch);
@@ -396,7 +400,8 @@ gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null,
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-only', test);
+gulp.task('test', gulp.series(clean, lint, 'test-only'));
gulp.task('test-coverage', gulp.series(clean, testCoverage));
gulp.task(viewCoverage);
@@ -407,7 +412,8 @@ 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('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watch)));
+gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast)));
+gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true}))));
gulp.task('serve-fake', gulp.series(clean, gulp.parallel('build-bundle-dev', watch), injectFakeServerEndpointDev, test, startFakeServer));
gulp.task('default', gulp.series(clean, makeWebpackPkg));
diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html
new file mode 100644
index 00000000000..e85ac8e1337
--- /dev/null
+++ b/integrationExamples/gpt/akamaidap_segments_example.html
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+Prebid.js Test
+Div-1
+
+
+
+Segments Sent to Bidding Adapter
+
+
+
diff --git a/integrationExamples/gpt/amp/creative.html b/integrationExamples/gpt/amp/creative.html
index 86f669dd6b5..384b81107cc 100644
--- a/integrationExamples/gpt/amp/creative.html
+++ b/integrationExamples/gpt/amp/creative.html
@@ -1,38 +1,16 @@
+
diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html
index 824d7a2f0c7..66e4a57d2a6 100644
--- a/integrationExamples/gpt/weboramaRtdProvider_example.html
+++ b/integrationExamples/gpt/weboramaRtdProvider_example.html
@@ -17,22 +17,31 @@
debug: true,
realTimeData: {
auctionDelay: 1000,
- dataProviders: [
- {
+ dataProviders: [{
name: "weborama",
waitForIt: true,
params: {
- weboCtxConf: {
- setTargeting: true,
- token: "to-be-defined",
- targetURL: "https://prebid.org/",
- defaultProfile: {
- webo_ctx: ['moon']
- }
- }
- }
- }
- ]
+ weboCtxConf: {
+ token: "to-be-defined", // mandatory
+ targetURL: "https://prebid.org", // default is document.URL
+ setPrebidTargeting: true, // default
+ sendToBidders: true, // default
+ defaultProfile: { // optional
+ webo_ctx: ['moon'],
+ webo_ds: ['bar']
+ }
+ },
+ weboUserDataConf: {
+ setPrebidTargeting: true, // default
+ sendToBidders: true, // default
+ defaultProfile: { // optional
+ webo_cs: ['Red'],
+ webo_audiences: ['bam']
+ },
+ localStorageProfileKey: 'webo_wam2gam_entry' // default
+ }
+ }
+ }]
}
});
});
@@ -54,7 +63,7 @@
}
},
bids: [{
- bidder: 'appnexus',
+ bidder: 'smartadserver',
params: {
placementId: 1
}
diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html
index fce46bb380f..610fb222787 100644
--- a/integrationExamples/gpt/x-domain/creative.html
+++ b/integrationExamples/gpt/x-domain/creative.html
@@ -2,37 +2,40 @@
// this script can be returned by an ad server delivering a cross domain iframe, into which the
// creative will be rendered, e.g. DFP delivering a SafeFrame
-let windowLocation = window.location;
-var urlParser = document.createElement('a');
+const windowLocation = window.location;
+const urlParser = document.createElement('a');
urlParser.href = '%%PATTERN:url%%';
-var publisherDomain = urlParser.protocol + '//' + urlParser.hostname;
+const publisherDomain = urlParser.protocol + '//' + urlParser.hostname;
+const adId = '%%PATTERN:hb_adid%%';
function renderAd(ev) {
- var key = ev.message ? 'message' : 'data';
- var adObject = {};
- try {
- adObject = JSON.parse(ev[key]);
- } catch (e) {
- return;
- }
+ const key = ev.message ? 'message' : 'data';
+ let adObject = {};
+ try {
+ adObject = JSON.parse(ev[key]);
+ } catch (e) {
+ return;
+ }
- var origin = ev.origin || ev.originalEvent.origin;
- if (adObject.message && adObject.message === 'Prebid Response' &&
- publisherDomain === origin &&
- adObject.adId === '%%PATTERN:hb_adid%%' &&
- (adObject.ad || adObject.adUrl)) {
- var body = window.document.body;
- var ad = adObject.ad;
- var url = adObject.adUrl;
- var width = adObject.width;
- var height = adObject.height;
+ const origin = ev.origin || ev.originalEvent.origin;
+ if (adObject.message && adObject.message === 'Prebid Response' &&
+ publisherDomain === origin &&
+ adObject.adId === adId) {
+ try {
+ const body = window.document.body;
+ const ad = adObject.ad;
+ const url = adObject.adUrl;
+ const width = adObject.width;
+ const height = adObject.height;
if (adObject.mediaType === 'video') {
+ signalRenderResult(false, {
+ reason: 'preventWritingOnMainDocument',
+ message: `Cannot render video ad ${adId}`
+ });
console.log('Error trying to write ad.');
- } else
-
- if (ad) {
- var frame = document.createElement('iframe');
+ } else if (ad) {
+ const frame = document.createElement('iframe');
frame.setAttribute('FRAMEBORDER', 0);
frame.setAttribute('SCROLLING', 'no');
frame.setAttribute('MARGINHEIGHT', 0);
@@ -46,18 +49,42 @@
frame.contentDocument.open();
frame.contentDocument.write(ad);
frame.contentDocument.close();
+ signalRenderResult(true);
} else if (url) {
body.insertAdjacentHTML('beforeend', '');
+ signalRenderResult(true);
} else {
- console.log('Error trying to write ad. No ad for bid response id: ' + id);
+ signalRenderResult(false, {
+ reason: 'noAd',
+ message: `No ad for ${adId}`
+ });
+ console.log(`Error trying to write ad. No ad markup or adUrl for ${adId}`);
}
+ } catch (e) {
+ signalRenderResult(false, {reason: 'exception', message: e.message});
+ console.log(`Error in rendering ad`, e);
}
}
+ function signalRenderResult(success, {reason, message} = {}) {
+ const payload = {
+ message: 'Prebid Event',
+ adId,
+ event: success ? 'adRenderSucceeded' : 'adRenderFailed',
+ }
+ if (!success) {
+ payload.info = {reason, message};
+ }
+ ev.source.postMessage(JSON.stringify(payload), publisherDomain);
+ }
+
+}
+
+
function requestAdFromPrebid() {
var message = JSON.stringify({
message: 'Prebid Request',
- adId: '%%PATTERN:hb_adid%%'
+ adId
});
window.parent.postMessage(message, publisherDomain);
}
diff --git a/karma.conf.maker.js b/karma.conf.maker.js
index cf5999ba85e..be51947dae8 100644
--- a/karma.conf.maker.js
+++ b/karma.conf.maker.js
@@ -111,7 +111,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) {
var webpackConfig = newWebpackConfig(codeCoverage);
var plugins = newPluginsArray(browserstack);
- var files = file ? ['test/helpers/prebidGlobal.js', file] : ['test/test_index.js'];
+ var files = file ? ['test/test_deps.js', file] : ['test/test_index.js'];
// This file opens the /debug.html tab automatically.
// It has no real value unless you're running --watch, and intend to do some debugging in the browser.
if (watchMode) {
@@ -166,7 +166,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) {
browserNoActivityTimeout: 3e5, // default 10000
captureTimeout: 3e5, // default 60000,
browserDisconnectTolerance: 3,
- concurrency: 5,
+ concurrency: 6,
plugins: plugins
}
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 3ac1e1ef59f..2e77873dc78 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -5,6 +5,7 @@
"akamaiDAPIdSystem",
"amxIdSystem",
"britepoolIdSystem",
+ "connectIdSystem",
"criteoIdSystem",
"deepintentDpesIdSystem",
"dmdIdSystem",
@@ -34,7 +35,8 @@
"uid2IdSystem",
"unifiedIdSystem",
"verizonMediaIdSystem",
- "zeotapIdPlusIdSystem"
+ "zeotapIdPlusIdSystem",
+ "adqueryIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js
index 2bdbdd6414b..af67bb2bf48 100644
--- a/modules/33acrossBidAdapter.js
+++ b/modules/33acrossBidAdapter.js
@@ -1,11 +1,20 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
import {
- deepAccess, uniques, isArray, getWindowTop, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, logWarn,
- getWindowSelf
+ deepAccess,
+ uniques,
+ isArray,
+ getWindowTop,
+ isGptPubadsDefined,
+ isSlotMatchingAdUnitCode,
+ logInfo,
+ logWarn,
+ getWindowSelf,
+ mergeDeep,
} from '../src/utils.js';
-import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+// **************************** UTILS *************************** //
const BIDDER_CODE = '33across';
const END_POINT = 'https://ssc.33across.com/api/v1/hb';
const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb';
@@ -42,6 +51,14 @@ const adapterState = {
const NON_MEASURABLE = 'nm';
+function getTTXConfig() {
+ const ttxSettings = Object.assign({},
+ config.getConfig('ttxSettings')
+ );
+
+ return ttxSettings;
+}
+
// **************************** VALIDATION *************************** //
function isBidRequestValid(bid) {
return (
@@ -74,6 +91,7 @@ function _validateGUID(bid) {
function _validateBanner(bid) {
const banner = deepAccess(bid, 'mediaTypes.banner');
+
// If there's no banner no need to validate against banner rules
if (banner === undefined) {
return true;
@@ -140,91 +158,125 @@ function _validateVideo(bid) {
// NOTE: With regards to gdrp consent data, the server will independently
// infer the gdpr applicability therefore, setting the default value to false
function buildRequests(bidRequests, bidderRequest) {
+ const {
+ ttxSettings,
+ gdprConsent,
+ uspConsent,
+ pageUrl
+ } = _buildRequestParams(bidRequests, bidderRequest);
+
+ const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests);
+
+ const serverRequests = [];
+
+ for (const key in groupedRequests) {
+ serverRequests.push(
+ _createServerRequest({
+ bidRequests: groupedRequests[key],
+ gdprConsent,
+ uspConsent,
+ pageUrl,
+ ttxSettings
+ })
+ )
+ }
+
+ return serverRequests;
+}
+
+function _buildRequestParams(bidRequests, bidderRequest) {
+ const ttxSettings = getTTXConfig();
+
const gdprConsent = Object.assign({
consentString: undefined,
gdprApplies: false
}, bidderRequest && bidderRequest.gdprConsent);
const uspConsent = bidderRequest && bidderRequest.uspConsent;
+
const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined);
adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques);
- return bidRequests.map(bidRequest => _createServerRequest(
- {
- bidRequest,
- gdprConsent,
- uspConsent,
- pageUrl
- })
- );
+ return {
+ ttxSettings,
+ gdprConsent,
+ uspConsent,
+ pageUrl
+ }
+}
+
+function _buildRequestGroups(ttxSettings, bidRequests) {
+ const bidRequestsComplete = bidRequests.map(_inferProduct);
+ const enableSRAMode = ttxSettings && ttxSettings.enableSRAMode;
+ const keyFunc = (enableSRAMode === true) ? _getSRAKey : _getMRAKey;
+
+ return _groupBidRequests(bidRequestsComplete, keyFunc);
+}
+
+function _groupBidRequests(bidRequests, keyFunc) {
+ const groupedRequests = {};
+
+ bidRequests.forEach((req) => {
+ const key = keyFunc(req);
+
+ groupedRequests[key] = groupedRequests[key] || [];
+ groupedRequests[key].push(req);
+ });
+
+ return groupedRequests;
+}
+
+function _getSRAKey(bidRequest) {
+ return `${bidRequest.params.siteId}:${bidRequest.params.productId}`;
+}
+
+function _getMRAKey(bidRequest) {
+ return `${bidRequest.bidId}`;
}
// 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 = {}, uspConsent, pageUrl}) {
+function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) {
const ttxRequest = {};
- const params = bidRequest.params;
+ const { siteId, test } = bidRequests[0].params;
/*
* Infer data for the request payload
*/
- ttxRequest.imp = [{}];
+ ttxRequest.imp = [];
- if (deepAccess(bidRequest, 'mediaTypes.banner')) {
- ttxRequest.imp[0].banner = {
- ..._buildBannerORTB(bidRequest)
- }
- }
-
- if (deepAccess(bidRequest, 'mediaTypes.video')) {
- ttxRequest.imp[0].video = _buildVideoORTB(bidRequest);
- }
-
- ttxRequest.imp[0].ext = {
- ttx: {
- prod: _getProduct(bidRequest)
- }
- };
+ bidRequests.forEach((req) => {
+ ttxRequest.imp.push(_buildImpORTB(req));
+ });
- ttxRequest.site = { id: params.siteId };
+ ttxRequest.site = { id: siteId };
if (pageUrl) {
ttxRequest.site.page = pageUrl;
}
- // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and
- // therefore in ad targetting process
- ttxRequest.id = bidRequest.bidId;
+ ttxRequest.id = bidRequests[0].auctionId;
if (gdprConsent.consentString) {
- ttxRequest.user = setExtension(
- ttxRequest.user,
- 'consent',
- gdprConsent.consentString
- )
+ ttxRequest.user = setExtensions(ttxRequest.user, {
+ 'consent': gdprConsent.consentString
+ });
}
- if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) {
- ttxRequest.user = setExtension(
- ttxRequest.user,
- 'eids',
- bidRequest.userIdAsEids
- )
+ if (Array.isArray(bidRequests[0].userIdAsEids) && bidRequests[0].userIdAsEids.length > 0) {
+ ttxRequest.user = setExtensions(ttxRequest.user, {
+ 'eids': bidRequests[0].userIdAsEids
+ });
}
- ttxRequest.regs = setExtension(
- ttxRequest.regs,
- 'gdpr',
- Number(gdprConsent.gdprApplies)
- );
+ ttxRequest.regs = setExtensions(ttxRequest.regs, {
+ 'gdpr': Number(gdprConsent.gdprApplies)
+ });
if (uspConsent) {
- ttxRequest.regs = setExtension(
- ttxRequest.regs,
- 'us_privacy',
- uspConsent
- )
+ ttxRequest.regs = setExtensions(ttxRequest.regs, {
+ 'us_privacy': uspConsent
+ });
}
ttxRequest.ext = {
@@ -237,16 +289,14 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl
}
};
- if (bidRequest.schain) {
- ttxRequest.source = setExtension(
- ttxRequest.source,
- 'schain',
- bidRequest.schain
- )
+ if (bidRequests[0].schain) {
+ ttxRequest.source = setExtensions(ttxRequest.source, {
+ 'schain': bidRequests[0].schain
+ });
}
// Finally, set the openRTB 'test' param if this is to be a test bid
- if (params.test === 1) {
+ if (test === 1) {
ttxRequest.test = 1;
}
@@ -259,8 +309,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl
};
// Allow the ability to configure the HB endpoint for testing purposes.
- const ttxSettings = config.getConfig('ttxSettings');
- const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`;
+ const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${siteId}`;
// Return the server request
return {
@@ -272,14 +321,36 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl
}
// BUILD REQUESTS: SET EXTENSIONS
-function setExtension(obj = {}, key, value) {
- return Object.assign({}, obj, {
- ext: Object.assign({}, obj.ext, {
- [key]: value
- })
+function setExtensions(obj = {}, extFields) {
+ return mergeDeep({}, obj, {
+ 'ext': extFields
});
}
+// BUILD REQUESTS: IMP
+function _buildImpORTB(bidRequest) {
+ const imp = {
+ id: bidRequest.bidId,
+ ext: {
+ ttx: {
+ prod: deepAccess(bidRequest, 'params.productId')
+ }
+ }
+ };
+
+ if (deepAccess(bidRequest, 'mediaTypes.banner')) {
+ imp.banner = {
+ ..._buildBannerORTB(bidRequest)
+ }
+ }
+
+ if (deepAccess(bidRequest, 'mediaTypes.video')) {
+ imp.video = _buildVideoORTB(bidRequest);
+ }
+
+ return imp;
+}
+
// BUILD REQUESTS: SIZE INFERENCE
function _transformSizes(sizes) {
if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) {
@@ -297,6 +368,14 @@ function _getSize(size) {
}
// BUILD REQUESTS: PRODUCT INFERENCE
+function _inferProduct(bidRequest) {
+ return mergeDeep({}, bidRequest, {
+ params: {
+ productId: _getProduct(bidRequest)
+ }
+ });
+}
+
function _getProduct(bidRequest) {
const { params, mediaTypes } = bidRequest;
@@ -367,7 +446,7 @@ function _buildVideoORTB(bidRequest) {
const video = {}
- const {w, h} = _getSize(videoParams.playerSize[0]);
+ const { w, h } = _getSize(videoParams.playerSize[0]);
video.w = w;
video.h = h;
@@ -388,11 +467,11 @@ function _buildVideoORTB(bidRequest) {
if (product === PRODUCT.INSTREAM) {
video.startdelay = video.startdelay || 0;
video.placement = 1;
- };
+ }
// bidfloors
if (typeof bidRequest.getFloor === 'function') {
- const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO);
+ const bidfloors = _getBidFloors(bidRequest, { w: video.w, h: video.h }, VIDEO);
if (bidfloors) {
Object.assign(video, {
@@ -404,6 +483,7 @@ function _buildVideoORTB(bidRequest) {
});
}
}
+
return video;
}
@@ -556,54 +636,61 @@ function _isIframe() {
}
// **************************** INTERPRET RESPONSE ******************************** //
-// NOTE: At this point, the response from 33exchange will only ever contain one bid
-// i.e. the highest bid
function interpretResponse(serverResponse, bidRequest) {
- const bidResponses = [];
-
- // If there are bids, look at the first bid of the first seatbid (see NOTE above for assumption about ttx)
- if (serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid.length > 0) {
- bidResponses.push(_createBidResponse(serverResponse.body));
- }
-
- return bidResponses;
+ const { seatbid, cur = 'USD' } = serverResponse.body;
+
+ if (!isArray(seatbid)) {
+ return [];
+ }
+
+ // Pick seats with valid bids and convert them into an Array of responses
+ // in format expected by Prebid Core
+ return seatbid
+ .filter((seat) => (
+ isArray(seat.bid) &&
+ seat.bid.length > 0
+ ))
+ .reduce((acc, seat) => {
+ return acc.concat(
+ seat.bid.map((bid) => _createBidResponse(bid, cur))
+ );
+ }, []);
}
-// All this assumes that only one bid is ever returned by ttx
-function _createBidResponse(response) {
+function _createBidResponse(bid, cur) {
const isADomainPresent =
- response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length;
- const bid = {
- requestId: response.id,
+ bid.adomain && bid.adomain.length;
+ const bidResponse = {
+ requestId: bid.impid,
bidderCode: BIDDER_CODE,
- cpm: response.seatbid[0].bid[0].price,
- width: response.seatbid[0].bid[0].w,
- height: response.seatbid[0].bid[0].h,
- ad: response.seatbid[0].bid[0].adm,
- ttl: response.seatbid[0].bid[0].ttl || 60,
- creativeId: response.seatbid[0].bid[0].crid,
- mediaType: deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER),
- currency: response.cur,
+ cpm: bid.price,
+ width: bid.w,
+ height: bid.h,
+ ad: bid.adm,
+ ttl: bid.ttl || 60,
+ creativeId: bid.crid,
+ mediaType: deepAccess(bid, 'ext.ttx.mediaType', BANNER),
+ currency: cur,
netRevenue: true
}
if (isADomainPresent) {
- bid.meta = {
- advertiserDomains: response.seatbid[0].bid[0].adomain
+ bidResponse.meta = {
+ advertiserDomains: bid.adomain
};
}
- if (bid.mediaType === VIDEO) {
- const vastType = deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml');
+ if (bidResponse.mediaType === VIDEO) {
+ const vastType = deepAccess(bid, 'ext.ttx.vastType', 'xml');
if (vastType === 'xml') {
- bid.vastXml = bid.ad;
+ bidResponse.vastXml = bidResponse.ad;
} else {
- bid.vastUrl = bid.ad;
+ bidResponse.vastUrl = bidResponse.ad;
}
}
- return bid;
+ return bidResponse;
}
// **************************** USER SYNC *************************** //
diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js
index 264cf5f9fcb..3381f00ff8f 100644
--- a/modules/adagioBidAdapter.js
+++ b/modules/adagioBidAdapter.js
@@ -268,8 +268,6 @@ function getSite(bidderRequest) {
} else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) {
// important note check if refererInfo.stack[0] is 'thruly' because a `null` value
// will be considered as "localhost" by the parseUrl function.
- // As the isBidRequestValid returns false when it does not reach the referer
- // this should never called.
const url = parseUrl(refererInfo.stack[0]);
domain = url.hostname;
}
@@ -308,7 +306,7 @@ function getElementFromTopWindow(element, currentWindow) {
function autoDetectAdUnitElementIdFromGpt(adUnitCode) {
const autoDetectedAdUnit = getGptSlotInfoForAdUnitCode(adUnitCode);
- if (autoDetectedAdUnit && autoDetectedAdUnit.divId) {
+ if (autoDetectedAdUnit.divId) {
return autoDetectedAdUnit.divId;
}
};
@@ -873,12 +871,6 @@ export const spec = {
autoFillParams(bid);
- if (!internal.getRefererInfo().reachedTop) {
- logWarn(`${LOG_PREFIX} the main page url is unreachabled.`);
- // internal.enqueue(debugData());
- return false;
- }
-
if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) {
logWarn(`${LOG_PREFIX} at least one required param is missing.`);
// internal.enqueue(debugData());
diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md
index 2779ced8cea..889822d9bd4 100644
--- a/modules/adagioBidAdapter.md
+++ b/modules/adagioBidAdapter.md
@@ -18,7 +18,7 @@ Below, the list of Adagio params and where they can be set.
| ---------- | ------------- | ------------- |
| siteId | x |
| organizationId (obsolete) | | x
-| site (obsolete) | | x
+| site (obsolete) | | x
| pagetype | x | x
| environment | x | x
| category | x | x
diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js
index ca4795c574f..1b93d4fe1c6 100644
--- a/modules/adbookpspBidAdapter.js
+++ b/modules/adbookpspBidAdapter.js
@@ -363,7 +363,7 @@ function impBidsToPrebidBids(
}
const impToPrebidBid =
- (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid) => {
+ (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => {
try {
const bidRequest = findBidRequest(bidderRequestBody, bid);
@@ -377,7 +377,7 @@ const impToPrebidBid =
let prebidBid = {
ad: bid.adm,
adId: bid.adid,
- adserverTargeting: targetingMap[bid.impid],
+ adserverTargeting: targetingMap[bidIndex],
adUnitCode: bidRequest.tagid,
bidderRequestId: bidderRequestBody.id,
bidId: bid.id,
@@ -408,6 +408,9 @@ const impToPrebidBid =
};
}
+ if (deepAccess(bid, 'ext.pa_win') === true) {
+ prebidBid.auctionWinner = true;
+ }
return prebidBid;
} catch (error) {
logError(`${BIDDER_CODE}: Error while building bid`, error);
@@ -429,29 +432,43 @@ function buildTargetingMap(bids) {
const values = impIds.reduce((result, id) => {
result[id] = {
lineItemIds: [],
+ orderIds: [],
dealIds: [],
adIds: [],
+ adAndOrderIndexes: []
};
return result;
}, {});
- bids.forEach((bid) => {
- values[bid.impid].lineItemIds.push(bid.ext.liid);
- values[bid.impid].dealIds.push(bid.dealid);
- values[bid.impid].adIds.push(bid.adid);
+ bids.forEach((bid, bidIndex) => {
+ let impId = bid.impid;
+ values[impId].lineItemIds.push(bid.ext.liid);
+ values[impId].dealIds.push(bid.dealid);
+ values[impId].adIds.push(bid.adid);
+
+ if (deepAccess(bid, 'ext.ordid')) {
+ values[impId].orderIds.push(bid.ext.ordid);
+ bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => {
+ let adIdIndex = values[impId].adIds.indexOf(bid.adid);
+ values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex)
+ })
+ }
});
const targetingMap = {};
- for (const id of impIds) {
- targetingMap[id] = {
+ bids.forEach((bid, bidIndex) => {
+ let id = bid.impid;
+
+ targetingMap[bidIndex] = {
hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR),
hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR),
+ hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR),
hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR),
+ hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR),
};
- }
-
+ })
return targetingMap;
}
diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js
index 18cafe829b5..dcc453ef35a 100644
--- a/modules/addefendBidAdapter.js
+++ b/modules/addefendBidAdapter.js
@@ -19,6 +19,7 @@ export const spec = {
v: $$PREBID_GLOBAL$$.version,
auctionId: false,
pageId: false,
+ gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true',
gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '',
referer: bidderRequest.refererInfo.referer,
bids: [],
diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js
index f7727a168b8..f0425a174ff 100644
--- a/modules/adfBidAdapter.js
+++ b/modules/adfBidAdapter.js
@@ -58,7 +58,11 @@ export const spec = {
aliases: BIDDER_ALIAS,
gvlid: GVLID,
supportedMediaTypes: [ NATIVE, BANNER, VIDEO ],
- isBidRequestValid: bid => !!bid.params.mid,
+ isBidRequestValid: (bid) => {
+ const params = bid.params || {};
+ const { mid, inv, mname } = params;
+ return !!(mid || (inv && mname));
+ },
buildRequests: (validBidRequests, bidderRequest) => {
let app, site;
@@ -104,12 +108,19 @@ export const spec = {
}) : {};
const bidfloor = floorInfo.floor;
const bidfloorcur = floorInfo.currency;
+ const { mid, inv, mname } = bid.params;
const imp = {
id: id + 1,
- tagid: bid.params.mid,
+ tagid: mid,
bidfloor,
- bidfloorcur
+ bidfloorcur,
+ ext: {
+ bidder: {
+ inv,
+ mname
+ }
+ }
};
const assets = _map(bid.nativeParams, (bidParams, key) => {
@@ -153,9 +164,6 @@ export const spec = {
assets
}
};
-
- bid.mediaType = NATIVE;
- return imp;
}
const bannerParams = deepAccess(bid, 'mediaTypes.banner');
@@ -172,18 +180,14 @@ export const spec = {
imp.banner = {
format
};
- bid.mediaType = BANNER;
-
- return imp;
}
const videoParams = deepAccess(bid, 'mediaTypes.video');
if (videoParams) {
imp.video = videoParams;
- bid.mediaType = VIDEO;
-
- return imp;
}
+
+ return imp;
});
const request = {
@@ -243,6 +247,7 @@ export const spec = {
return bids.map((bid, id) => {
const bidResponse = bidResponses[id];
if (bidResponse) {
+ const mediaType = deepAccess(bidResponse, 'ext.prebid.type');
const result = {
requestId: bid.bidId,
cpm: bidResponse.price,
@@ -250,12 +255,12 @@ export const spec = {
ttl: 360,
netRevenue: bid.netRevenue === 'net',
currency: cur,
- mediaType: bid.mediaType,
+ mediaType,
width: bidResponse.w,
height: bidResponse.h,
dealId: bidResponse.dealid,
meta: {
- mediaType: bid.mediaType,
+ mediaType,
advertiserDomains: bidResponse.adomain
}
};
@@ -263,10 +268,10 @@ export const spec = {
if (bidResponse.native) {
result.native = parseNative(bidResponse);
} else {
- result[ bid.mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm;
+ result[ mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm;
}
- if (!bid.renderer && bid.mediaType === VIDEO && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') {
+ if (!bid.renderer && mediaType === VIDEO && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') {
result.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode});
result.renderer.setRender(renderer);
}
diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js
index 2696915ea0a..4dd320d3f24 100644
--- a/modules/adgenerationBidAdapter.js
+++ b/modules/adgenerationBidAdapter.js
@@ -1,4 +1,4 @@
-import { tryAppendQueryString, getBidIdParameter } from '../src/utils.js';
+import {tryAppendQueryString, getBidIdParameter} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, NATIVE} from '../src/mediaTypes.js';
import {config} from '../src/config.js';
@@ -25,7 +25,7 @@ export const spec = {
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (validBidRequests, bidderRequest) {
- const ADGENE_PREBID_VERSION = '1.1.0';
+ const ADGENE_PREBID_VERSION = '1.2.0';
let serverRequests = [];
for (let i = 0, len = validBidRequests.length; i < len; i++) {
const validReq = validBidRequests[i];
@@ -118,13 +118,25 @@ export const spec = {
function createAd(body, bidRequest) {
let ad = body.ad;
if (body.vastxml && body.vastxml.length > 0) {
- ad = `${createAPVTag()}${insertVASTMethod(bidRequest.bidId, body.vastxml)}`;
+ if (isUpperBillboard(body)) {
+ const marginTop = bidRequest.params.marginTop ? bidRequest.params.marginTop : '0';
+ ad = `${createADGBrowserMTag()}${insertVASTMethodForADGBrowserM(body.vastxml, marginTop)}`;
+ } else {
+ ad = `${createAPVTag()}${insertVASTMethodForAPV(bidRequest.bidId, body.vastxml)}`;
+ }
}
ad = appendChildToBody(ad, body.beacon);
if (removeWrapper(ad)) return removeWrapper(ad);
return ad;
}
+function isUpperBillboard(body) {
+ if (body.location_params && body.location_params.option && body.location_params.option.ad_type) {
+ return body.location_params.option.ad_type === 'upper_billboard';
+ }
+ return false;
+}
+
function isNative(body) {
if (!body) return false;
return body.native_ad && body.native_ad.assets.length > 0;
@@ -190,7 +202,12 @@ function createAPVTag() {
return apvScript.outerHTML;
}
-function insertVASTMethod(targetId, vastXml) {
+function createADGBrowserMTag() {
+ const ADGBrowserMURL = 'https://i.socdm.com/sdk/js/adg-browser-m.js';
+ return ``;
+}
+
+function insertVASTMethodForAPV(targetId, vastXml) {
let apvVideoAdParam = {
s: targetId
};
@@ -200,6 +217,13 @@ function insertVASTMethod(targetId, vastXml) {
return script.outerHTML;
}
+function insertVASTMethodForADGBrowserM(vastXml, marginTop) {
+ const script = document.createElement(`script`);
+ script.type = 'text/javascript';
+ script.innerHTML = `window.ADGBrowserM.init({vastXml: '${vastXml.replace(/\r?\n/g, '')}', marginTop: '${marginTop}'});`;
+ return script.outerHTML;
+}
+
/**
*
* @param ad
diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js
index c94a4e35efd..6a8c98650c0 100644
--- a/modules/adhashBidAdapter.js
+++ b/modules/adhashBidAdapter.js
@@ -6,7 +6,7 @@ const VERSION = '1.0';
export const spec = {
code: 'adhash',
- url: 'https://bidder.adhash.org/rtb?version=' + VERSION + '&prebid=true',
+ url: 'https://bidder.adhash.com/rtb?version=' + VERSION + '&prebid=true',
supportedMediaTypes: [ BANNER ],
isBidRequestValid: (bid) => {
@@ -37,7 +37,7 @@ export const spec = {
var size = validBidRequests[i].sizes[index].join('x');
bidRequests.push({
method: 'POST',
- url: url,
+ url: url + '&publisher=' + validBidRequests[i].params.publisherId,
bidRequest: validBidRequests[i],
data: {
timezone: new Date().getTimezoneOffset() / 60,
@@ -87,7 +87,7 @@ export const spec = {
cpm: responseBody.creatives[0].costEUR,
ad:
`
-
+
`,
width: request.bidRequest.sizes[0][0],
height: request.bidRequest.sizes[0][1],
diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js
index 88f3e0e0e4f..145b5605bc2 100644
--- a/modules/adheseBidAdapter.js
+++ b/modules/adheseBidAdapter.js
@@ -2,6 +2,7 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
const BIDDER_CODE = 'adhese';
const GVLID = 553;
@@ -20,11 +21,15 @@ export const spec = {
if (validBidRequests.length === 0) {
return null;
}
+
const { gdprConsent, refererInfo } = bidderRequest;
+ const adheseConfig = config.getConfig('adhese');
const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {};
const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {};
- const commonParams = { ...gdprParams, ...refererParams };
+ const globalCustomParams = (adheseConfig && adheseConfig.globalTargets) ? cleanTargets(adheseConfig.globalTargets) : {};
+ const commonParams = { ...globalCustomParams, ...gdprParams, ...refererParams };
+ const vastContentAsUrl = !(adheseConfig && adheseConfig.vastContentAsUrl == false);
const slots = validBidRequests.map(bid => ({
slotname: bidToSlotName(bid),
@@ -34,7 +39,7 @@ export const spec = {
const payload = {
slots: slots,
parameters: commonParams,
- vastContentAsUrl: true,
+ vastContentAsUrl: vastContentAsUrl,
user: {
ext: {
eids: getEids(validBidRequests),
diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 7c685a64255..c23eca2f96a 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -1,6 +1,6 @@
import {
isStr, isArray, isPlainObject, deepSetValue, isNumber, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize,
- cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums
+ cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums, mergeDeep, isEmpty, inIframe
} from '../src/utils.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
@@ -75,7 +75,10 @@ export const spec = {
{code: 'denakop'},
{code: 'rtbanalytica'},
{code: 'unibots'},
- {code: 'ergadx'}
+ {code: 'catapultx'},
+ {code: 'ergadx'},
+ {code: 'turktelekom'},
+ {code: 'felixads'}
],
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
@@ -101,17 +104,16 @@ export const spec = {
* @returns {ServerRequest[]}
*/
buildRequests: function (bidRequests, bidderRequest) {
- let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo);
+ let impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo);
let requests = [];
let schain = bidRequests[0].schain;
- Object.keys(impDispatch).forEach(host => {
- Object.keys(impDispatch[host]).forEach(zoneId => {
- const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain);
- requests.push({
- method: 'POST',
- url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`,
- data: JSON.stringify(request)
- });
+ _each(impGroups, impGroup => {
+ let {host, zoneId, imps} = impGroup;
+ const request = buildRtbRequest(imps, bidderRequest, schain);
+ requests.push({
+ method: 'POST',
+ url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`,
+ data: JSON.stringify(request)
});
});
return requests;
@@ -207,17 +209,19 @@ registerBidder(spec);
* @param bidRequests {BidRequest[]}
* @param refererInfo {refererInfo}
*/
-function dispatchImps(bidRequests, refererInfo) {
+function groupImpressionsByHostZone(bidRequests, refererInfo) {
let secure = (refererInfo && refererInfo.referer.indexOf('https:') === 0);
- return bidRequests.map(bidRequest => buildImp(bidRequest, secure))
- .reduce((acc, curr, index) => {
- let bidRequest = bidRequests[index];
- let {zoneId, host} = bidRequest.params;
- acc[host] = acc[host] || {};
- acc[host][zoneId] = acc[host][zoneId] || [];
- acc[host][zoneId].push(curr);
- return acc;
- }, {});
+ return Object.values(
+ bidRequests.map(bidRequest => buildImp(bidRequest, secure))
+ .reduce((acc, curr, index) => {
+ let bidRequest = bidRequests[index];
+ let {zoneId, host} = bidRequest.params;
+ let key = `${host}_${zoneId}`;
+ acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []};
+ acc[key].imps.push(curr);
+ return acc;
+ }, {})
+ );
}
function getBidFloor(bid, mediaType, sizes) {
@@ -363,57 +367,142 @@ function getAllowedSyncMethod(bidderCode) {
}
/**
- * Builds complete rtb request
- * @param imps {Object} Collection of rtb impressions
- * @param bidderRequest {BidderRequest}
- * @param schain {Object=} Supply chain config
- * @return {Object} Complete rtb request
+ * Create device object from fpd and host-collected data
+ * @param fpd {Object}
+ * @returns {{device: Object}}
*/
-function buildRtbRequest(imps, bidderRequest, schain) {
- let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest;
- let coppa = config.getConfig('coppa');
- let req = {
- 'id': auctionId,
- 'imp': imps,
- 'site': createSite(refererInfo),
- 'at': 1,
- 'device': {
- 'ip': 'caller',
- 'ipv6': 'caller',
- 'ua': 'caller',
- 'js': 1,
- 'language': getLanguage()
- },
- 'tmax': parseInt(timeout)
- };
+function makeDevice(fpd) {
+ let device = mergeDeep({
+ 'ip': 'caller',
+ 'ipv6': 'caller',
+ 'ua': 'caller',
+ 'js': 1,
+ 'language': getLanguage()
+ }, fpd.device || {});
if (getDNT()) {
- req.device.dnt = 1;
+ device.dnt = 1;
+ }
+ return {device: device};
+}
+
+/**
+ * Create site or app description object
+ * @param bidderRequest {BidderRequest}
+ * @param fpd {Object}
+ * @returns {{site: Object}|{app: Object}}
+ */
+function makeSiteOrApp(bidderRequest, fpd) {
+ let {refererInfo} = bidderRequest;
+ let appConfig = config.getConfig('app');
+ if (isEmpty(appConfig)) {
+ return {site: createSite(refererInfo, fpd)}
+ } else {
+ return {app: appConfig};
}
+}
+
+/**
+ * Create user description object
+ * @param bidderRequest {BidderRequest}
+ * @param fpd {Object}
+ * @returns {{user: Object} | undefined}
+ */
+function makeUser(bidderRequest, fpd) {
+ let {gdprConsent} = bidderRequest;
+ let user = fpd.user || {};
+ if (gdprConsent && gdprConsent.consentString !== undefined) {
+ deepSetValue(user, 'ext.consent', gdprConsent.consentString);
+ }
+ let eids = getExtendedUserIds(bidderRequest);
+ if (eids) {
+ deepSetValue(user, 'ext.eids', eids);
+ }
+ if (!isEmpty(user)) { return {user: user}; }
+}
+
+/**
+ * Create privacy regulations object
+ * @param bidderRequest {BidderRequest}
+ * @returns {{regs: Object} | undefined}
+ */
+function makeRegulations(bidderRequest) {
+ let {gdprConsent, uspConsent} = bidderRequest;
+ let regs = {};
if (gdprConsent) {
if (gdprConsent.gdprApplies !== undefined) {
- deepSetValue(req, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies);
- }
- if (gdprConsent.consentString !== undefined) {
- deepSetValue(req, 'user.ext.consent', gdprConsent.consentString);
+ deepSetValue(regs, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies);
}
}
if (uspConsent) {
- deepSetValue(req, 'regs.ext.us_privacy', uspConsent);
+ deepSetValue(regs, 'regs.ext.us_privacy', uspConsent);
+ }
+ if (config.getConfig('coppa')) {
+ deepSetValue(regs, 'regs.coppa', 1);
}
- if (coppa) {
- deepSetValue(req, 'regs.coppa', 1);
+ if (!isEmpty(regs)) {
+ return regs;
}
+}
+
+/**
+ * Create top-level request object
+ * @param bidderRequest {BidderRequest}
+ * @param imps {Object} Impressions
+ * @param fpd {Object} First party data
+ * @returns
+ */
+function makeBaseRequest(bidderRequest, imps, fpd) {
+ let {auctionId, timeout} = bidderRequest;
+ let request = {
+ 'id': auctionId,
+ 'imp': imps,
+ 'at': 1,
+ 'tmax': parseInt(timeout)
+ };
+ if (!isEmpty(fpd.bcat)) {
+ request.bcat = fpd.bcat;
+ }
+ if (!isEmpty(fpd.badv)) {
+ request.badv = fpd.badv;
+ }
+ return request;
+}
+
+/**
+ * Initialize sync capabilities
+ * @param bidderRequest {BidderRequest}
+ */
+function makeSyncInfo(bidderRequest) {
+ let {bidderCode} = bidderRequest;
let syncMethod = getAllowedSyncMethod(bidderCode);
if (syncMethod) {
- deepSetValue(req, 'ext.adk_usersync', syncMethod);
+ let res = {};
+ deepSetValue(res, 'ext.adk_usersync', syncMethod);
+ return res;
}
+}
+
+/**
+ * Builds complete rtb request
+ * @param imps {Object} Collection of rtb impressions
+ * @param bidderRequest {BidderRequest}
+ * @param schain {Object=} Supply chain config
+ * @return {Object} Complete rtb request
+ */
+function buildRtbRequest(imps, bidderRequest, schain) {
+ let fpd = config.getConfig('ortb2') || {};
+
+ let req = mergeDeep(
+ makeBaseRequest(bidderRequest, imps, fpd),
+ makeDevice(fpd),
+ makeSiteOrApp(bidderRequest, fpd),
+ makeUser(bidderRequest, fpd),
+ makeRegulations(bidderRequest),
+ makeSyncInfo(bidderRequest)
+ );
if (schain) {
deepSetValue(req, 'source.ext.schain', schain);
}
- let eids = getExtendedUserIds(bidderRequest);
- if (eids) {
- deepSetValue(req, 'user.ext.eids', eids);
- }
return req;
}
@@ -429,18 +518,17 @@ function getLanguage() {
/**
* Creates site description object
*/
-function createSite(refInfo) {
+function createSite(refInfo, fpd) {
let url = parseUrl(refInfo.referer);
let site = {
'domain': url.hostname,
'page': `${url.protocol}://${url.hostname}${url.pathname}`
};
- if (self === top && document.referrer) {
+ mergeDeep(site, fpd.site);
+ if (!inIframe() && document.referrer) {
site.ref = document.referrer;
- }
- let keywords = document.getElementsByTagName('meta')['keywords'];
- if (keywords && keywords.content) {
- site.keywords = keywords.content;
+ } else {
+ delete site.ref;
}
return site;
}
diff --git a/modules/adlivetechBidAdapter.md b/modules/adlivetechBidAdapter.md
new file mode 100644
index 00000000000..612e669ea1a
--- /dev/null
+++ b/modules/adlivetechBidAdapter.md
@@ -0,0 +1,61 @@
+# Overview
+
+Module Name: Adlivetech Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: grid-tech@themediagrid.com
+
+# Description
+
+Module that connects to Grid demand source to fetch bids.
+The adapter is GDPR compliant and supports banner and video (instream and outstream).
+
+# Test Parameters
+```
+ var adUnits = [
+ {
+ code: 'test-div',
+ sizes: [[300, 250]],
+ bids: [
+ {
+ bidder: "adlivetech",
+ params: {
+ uid: '1',
+ bidFloor: 0.5
+ }
+ }
+ ]
+ },{
+ code: 'test-div',
+ sizes: [[728, 90]],
+ bids: [
+ {
+ bidder: "adlivetech",
+ params: {
+ uid: 2,
+ keywords: {
+ brandsafety: ['disaster'],
+ topic: ['stress', 'fear']
+ }
+ }
+ }
+ ]
+ },
+ {
+ code: 'test-div',
+ sizes: [[728, 90]],
+ mediaTypes: { video: {
+ context: 'instream',
+ playerSize: [728, 90],
+ mimes: ['video/mp4']
+ },
+ bids: [
+ {
+ bidder: "adlivetech",
+ params: {
+ uid: 11
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/modules/adlooxAdServerVideo.js b/modules/adlooxAdServerVideo.js
index 7305283039c..bd715cb34f3 100644
--- a/modules/adlooxAdServerVideo.js
+++ b/modules/adlooxAdServerVideo.js
@@ -9,7 +9,7 @@
import { registerVideoSupport } from '../src/adServerManager.js';
import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js';
import { ajax } from '../src/ajax.js';
-import { EVENTS } from '../src/constants.json';
+import CONSTANTS from '../src/constants.json';
import { targeting } from '../src/targeting.js';
import { logInfo, isFn, logError, isPlainObject, isStr, isBoolean, deepSetValue, deepClone, timestamp, logWarn } from '../src/utils.js';
@@ -74,7 +74,7 @@ function track(options, callback) {
bid.ext.adloox.video.adserver = false;
analyticsCommand(COMMAND.TRACK, {
- eventType: EVENTS.BID_WON,
+ eventType: CONSTANTS.EVENTS.BID_WON,
args: bid
});
}
diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js
index 6ea1df1b72c..781b8db830a 100644
--- a/modules/adlooxAnalyticsAdapter.js
+++ b/modules/adlooxAnalyticsAdapter.js
@@ -9,7 +9,7 @@ import adapter from '../src/AnalyticsAdapter.js';
import { loadExternalScript } from '../src/adloader.js';
import { auctionManager } from '../src/auctionManager.js';
import { AUCTION_COMPLETED } from '../src/auction.js';
-import { EVENTS } from '../src/constants.json';
+import CONSTANTS from '../src/constants.json';
import find from 'core-js-pure/features/array/find.js';
import {
deepAccess, logInfo, isPlainObject, logError, isStr, isNumber, getGptSlotInfoForAdUnitCode,
@@ -199,9 +199,9 @@ analyticsAdapter.url = function(url, args, bid) {
return url + a2qs(args);
}
-analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) {
+analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = function(auctionDetails) {
if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return;
- analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP;
+ analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = NOOP;
logMessage(MODULE, 'preloading verification JS');
@@ -214,7 +214,7 @@ analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) {
insertElement(link);
}
-analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) {
+analyticsAdapter[`handle_${CONSTANTS.EVENTS.BID_WON}`] = function(bid) {
if (deepAccess(bid, 'ext.adloox.video.adserver')) {
logMessage(MODULE, `measuring '${bid.mediaType}' ad unit code '${bid.adUnitCode}' via Ad Server module`);
return;
diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js
index e02a1a9df04..666e9aea309 100644
--- a/modules/admanBidAdapter.js
+++ b/modules/admanBidAdapter.js
@@ -1,10 +1,11 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
import { isFn, deepAccess, logMessage } from '../src/utils.js';
+import {config} from '../src/config.js';
const BIDDER_CODE = 'adman';
const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi';
-const URL_SYNC = 'https://pub.admanmedia.com/?c=o&m=sync';
+const URL_SYNC = 'https://pub.admanmedia.com';
function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId ||
@@ -108,6 +109,7 @@ export const spec = {
}
if (bid.userId) {
getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com');
+ getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com');
}
if (traff === VIDEO) {
placement.playerSize = bid.mediaTypes[VIDEO].playerSize;
@@ -151,19 +153,24 @@ export const spec = {
},
getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
- let syncUrl = URL_SYNC
+ let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image';
+ let syncUrl = URL_SYNC + `/${syncType}?pbjs=1`;
if (gdprConsent && gdprConsent.consentString) {
if (typeof gdprConsent.gdprApplies === 'boolean') {
syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
} else {
- syncUrl += `&gdpr==0&gdpr_consent=${gdprConsent.consentString}`;
+ syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
}
}
if (uspConsent && uspConsent.consentString) {
syncUrl += `&ccpa_consent=${uspConsent.consentString}`;
}
+
+ const coppa = config.getConfig('coppa') ? 1 : 0;
+ syncUrl += `&coppa=${coppa}`;
+
return [{
- type: 'image',
+ type: syncType,
url: syncUrl
}];
}
diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js
index e136dfcfbdb..dfb76a03804 100644
--- a/modules/admixerBidAdapter.js
+++ b/modules/admixerBidAdapter.js
@@ -1,14 +1,15 @@
import { logError } from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {config} from '../src/config.js';
+import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js';
const BIDDER_CODE = 'admixer';
-const ALIASES = ['go2net', 'adblender', 'adsyield'];
+const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads'];
const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx';
export const spec = {
code: BIDDER_CODE,
aliases: ALIASES,
- supportedMediaTypes: ['banner', 'video'],
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
/**
* Determines whether or not the given bid request is valid.
*/
@@ -19,9 +20,20 @@ export const spec = {
* Make a server request from the list of BidRequests.
*/
buildRequests: function (validRequest, bidderRequest) {
+ let w;
+ let docRef;
+ do {
+ w = w ? w.parent : window;
+ try {
+ docRef = w.document.referrer;
+ } catch (e) {
+ break;
+ }
+ } while (w !== window.top);
const payload = {
imps: [],
ortb2: config.getConfig('ortb2'),
+ docReferrer: docRef,
};
let endpointUrl;
if (bidderRequest) {
diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js
index a1dff3d258d..f05cd9f9f32 100644
--- a/modules/adnuntiusBidAdapter.js
+++ b/modules/adnuntiusBidAdapter.js
@@ -37,7 +37,8 @@ const handleMeta = function () {
}
const getUsi = function (meta, ortb2, bidderRequest) {
- const usi = (meta !== null) ? meta.usi : false;
+ let usi = (meta !== null && meta.usi) ? meta.usi : false;
+ if (ortb2 && ortb2.user && ortb2.user.id) { usi = ortb2.user.id }
return usi
}
@@ -55,6 +56,8 @@ export const spec = {
const requests = [];
const request = [];
const ortb2 = config.getConfig('ortb2');
+ const bidderConfig = config.getConfig();
+
const adnMeta = handleMeta()
const usi = getUsi(adnMeta, ortb2, bidderRequest)
const segments = getSegmentsFromOrtb(ortb2);
@@ -67,7 +70,7 @@ export const spec = {
if (gdprApplies !== undefined) request.push('consentString=' + consentString);
if (segments.length > 0) request.push('segments=' + segments.join(','));
if (usi) request.push('userId=' + usi);
-
+ if (bidderConfig.useCookie === false) request.push('noCookies=true')
for (var i = 0; i < validBidRequests.length; i++) {
const bid = validBidRequests[i]
const network = bid.params.network || 'network';
diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js
index 5bbee86df54..e81f6e50054 100644
--- a/modules/adomikAnalyticsAdapter.js
+++ b/modules/adomikAnalyticsAdapter.js
@@ -12,6 +12,9 @@ const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED;
const bidResponse = CONSTANTS.EVENTS.BID_RESPONSE;
const bidWon = CONSTANTS.EVENTS.BID_WON;
const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT;
+const ua = navigator.userAgent;
+
+var _sampled = true;
let adomikAdapter = Object.assign(adapter({}),
{
@@ -47,7 +50,7 @@ let adomikAdapter = Object.assign(adapter({}),
type: 'request',
event: {
bidder: bid.bidder.toUpperCase(),
- placementCode: bid.placementCode
+ placementCode: bid.adUnitCode
}
});
});
@@ -67,13 +70,20 @@ adomikAdapter.initializeBucketEvents = function() {
adomikAdapter.bucketEvents = [];
}
+adomikAdapter.maxPartLength = function () {
+ return (ua.includes(' MSIE ')) ? 1600 : 60000;
+};
+
adomikAdapter.sendTypedEvent = function() {
const groupedTypedEvents = adomikAdapter.buildTypedEvents();
const bulkEvents = {
+ testId: adomikAdapter.currentContext.testId,
+ testValue: adomikAdapter.currentContext.testValue,
uid: adomikAdapter.currentContext.uid,
ahbaid: adomikAdapter.currentContext.id,
hostname: window.location.hostname,
+ sampling: adomikAdapter.currentContext.sampling,
eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) {
let sizes = [];
const eventKeys = ['request', 'response', 'winner'];
@@ -108,9 +118,10 @@ adomikAdapter.sendTypedEvent = function() {
// Encode object in base64
const encodedBuf = window.btoa(stringBulkEvents);
- // Create final url and split it in 1600 characters max (+endpoint length)
+ // Create final url and split it (+endpoint length)
const encodedUri = encodeURIComponent(encodedBuf);
- const splittedUrl = encodedUri.match(/.{1,1600}/g);
+ const maxLength = adomikAdapter.maxPartLength();
+ const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g'));
splittedUrl.forEach((split, i) => {
const partUrl = `${split}&id=${adomikAdapter.currentContext.id}&part=${i}&on=${splittedUrl.length - 1}`;
@@ -120,8 +131,10 @@ adomikAdapter.sendTypedEvent = function() {
};
adomikAdapter.sendWonEvent = function (wonEvent) {
+ let keyValues = { testId: adomikAdapter.currentContext.testId, testValue: adomikAdapter.currentContext.testValue }
+ wonEvent = {...wonEvent, ...keyValues}
const stringWonEvent = JSON.stringify(wonEvent)
- logInfo('Won event sent to adomik prebid analytic ' + wonEvent);
+ logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent);
// Encode object in base64
const encodedBuf = window.btoa(stringWonEvent);
@@ -193,17 +206,28 @@ adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics;
adomikAdapter.enableAnalytics = function (config) {
adomikAdapter.currentContext = {};
-
const initOptions = config.options;
- if (initOptions) {
- adomikAdapter.currentContext = {
- uid: initOptions.id,
- url: initOptions.url,
- id: '',
- timeouted: false,
+
+ _sampled = typeof config === 'undefined' ||
+ typeof config.sampling === 'undefined' ||
+ Math.random() < parseFloat(config.sampling);
+
+ if (_sampled) {
+ if (initOptions) {
+ adomikAdapter.currentContext = {
+ uid: initOptions.id,
+ url: initOptions.url,
+ testId: initOptions.testId,
+ testValue: initOptions.testValue,
+ id: '',
+ timeouted: false,
+ sampling: config.sampling
+ }
+ logInfo('Adomik Analytics enabled with config', initOptions);
+ adomikAdapter.adapterEnableAnalytics(config);
}
- logInfo('Adomik Analytics enabled with config', initOptions);
- adomikAdapter.adapterEnableAnalytics(config);
+ } else {
+ logInfo('Adomik Analytics ignored for sampling', config.sampling);
}
};
diff --git a/modules/adplusBidAdapter.js b/modules/adplusBidAdapter.js
new file mode 100644
index 00000000000..c001781a792
--- /dev/null
+++ b/modules/adplusBidAdapter.js
@@ -0,0 +1,203 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import * as utils from '../src/utils.js';
+import { BANNER } from '../src/mediaTypes.js';
+import { getStorageManager } from '../src/storageManager.js';
+
+// #region Constants
+export const BIDDER_CODE = 'adplus';
+export const ADPLUS_ENDPOINT = 'https://ssp.ad-plus.com.tr/server/headerBidding';
+export const DGID_CODE = 'adplus_dg_id';
+export const SESSION_CODE = 'adplus_s_id';
+export const storage = getStorageManager(undefined, BIDDER_CODE);
+const COOKIE_EXP = 1000 * 60 * 60 * 24; // 1 day
+// #endregion
+
+// #region Helpers
+export function isValidUuid (uuid) {
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
+ uuid
+ );
+}
+
+function getSessionId() {
+ let sid = storage.cookiesAreEnabled() && storage.getCookie(SESSION_CODE);
+
+ if (
+ !sid || !isValidUuid(sid)
+ ) {
+ sid = utils.generateUUID();
+ setSessionId(sid);
+ }
+
+ return sid;
+}
+
+function setSessionId(sid) {
+ if (storage.cookiesAreEnabled()) {
+ const expires = new Date(Date.now() + COOKIE_EXP).toISOString();
+
+ storage.setCookie(SESSION_CODE, sid, expires);
+ }
+}
+// #endregion
+
+// #region Bid request validation
+function isBidRequestValid(bid) {
+ if (!bid) {
+ utils.logError(BIDDER_CODE, 'bid, can not be empty', bid);
+ return false;
+ }
+
+ if (!bid.params) {
+ utils.logError(BIDDER_CODE, 'bid.params is required.');
+ return false;
+ }
+
+ if (!bid.params.adUnitId || typeof bid.params.adUnitId !== 'string') {
+ utils.logError(
+ BIDDER_CODE,
+ 'bid.params.adUnitId is missing or has wrong type.'
+ );
+ return false;
+ }
+
+ if (!bid.params.inventoryId || typeof bid.params.inventoryId !== 'string') {
+ utils.logError(
+ BIDDER_CODE,
+ 'bid.params.inventoryId is missing or has wrong type.'
+ );
+ return false;
+ }
+
+ if (
+ !bid.mediaTypes ||
+ !bid.mediaTypes[BANNER] ||
+ !utils.isArray(bid.mediaTypes[BANNER].sizes) ||
+ bid.mediaTypes[BANNER].sizes.length <= 0 ||
+ !utils.isArrayOfNums(bid.mediaTypes[BANNER].sizes[0])
+ ) {
+ utils.logError(BIDDER_CODE, 'Wrong or missing size parameters.');
+ return false;
+ }
+
+ return true;
+}
+// #endregion
+
+// #region Building the bid requests
+/**
+ *
+ * @param {object} bid
+ * @returns
+ */
+function createBidRequest(bid) {
+ // Developer Params
+ const {
+ inventoryId,
+ adUnitId,
+ extraData,
+ yearOfBirth,
+ gender,
+ categories,
+ latitude,
+ longitude,
+ sdkVersion,
+ } = bid.params;
+
+ return {
+ method: 'GET',
+ url: ADPLUS_ENDPOINT,
+ data: utils.cleanObj({
+ bidId: bid.bidId,
+ inventoryId,
+ adUnitId,
+ adUnitWidth: bid.mediaTypes[BANNER].sizes[0][0],
+ adUnitHeight: bid.mediaTypes[BANNER].sizes[0][1],
+ extraData,
+ yearOfBirth,
+ gender,
+ categories,
+ latitude,
+ longitude,
+ sdkVersion: sdkVersion || '1',
+ session: getSessionId(),
+ interstitial: 0,
+ token: typeof window.top === 'object' && window.top[DGID_CODE] ? window.top[DGID_CODE] : undefined,
+ secure: window.location.protocol === 'https:' ? 1 : 0,
+ screenWidth: screen.width,
+ screenHeight: screen.height,
+ language: window.navigator.language || 'en-US',
+ pageUrl: window.location.href,
+ domain: window.location.hostname,
+ referrer: window.location.referrer,
+ }),
+ };
+}
+
+function buildRequests(validBidRequests, bidderRequest) {
+ return validBidRequests.map((req) => createBidRequest(req));
+}
+// #endregion
+
+// #region Interpreting Responses
+/**
+ *
+ * @param {HeaderBiddingResponse} responseData
+ * @param { object } bidParams
+ * @returns
+ */
+function createAdResponse(responseData, bidParams) {
+ return {
+ requestId: responseData.requestID,
+ cpm: responseData.cpm,
+ currency: responseData.currency,
+ width: responseData.width,
+ height: responseData.height,
+ creativeId: responseData.creativeID,
+ dealId: responseData.dealID,
+ netRevenue: responseData.netRevenue,
+ ttl: responseData.ttl,
+ ad: responseData.ad,
+ mediaType: responseData.mediaType,
+ meta: {
+ advertiserDomains: responseData.advertiserDomains,
+ primaryCatId: utils.isArray(responseData.categoryIDs) && responseData.categoryIDs.length > 0
+ ? responseData.categoryIDs[0] : undefined,
+ secondaryCatIds: responseData.categoryIDs,
+ },
+ };
+}
+
+function interpretResponse(response, request) {
+ // In case of empty response
+ if (
+ response.body == null ||
+ !utils.isArray(response.body) ||
+ response.body.length === 0
+ ) {
+ return [];
+ }
+ const bids = response.body.map((bid) => createAdResponse(bid));
+ return bids;
+}
+// #endregion
+
+// #region Bidder
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER],
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ onTimeout(timeoutData) {
+ utils.logError('Adplus adapter timed out for the auction.', timeoutData);
+ },
+ onBidWon(bid) {
+ utils.logInfo(
+ `Adplus adapter won the auction. Bid id: ${bid.bidId}, Ad Unit Id: ${bid.adUnitId}, Inventory Id: ${bid.inventoryId}`
+ );
+ },
+};
+
+registerBidder(spec);
+// #endregion
diff --git a/modules/adplusBidAdapter.md b/modules/adplusBidAdapter.md
new file mode 100644
index 00000000000..dce9e4a312f
--- /dev/null
+++ b/modules/adplusBidAdapter.md
@@ -0,0 +1,39 @@
+# Overview
+
+Module Name: AdPlus Bidder Adapter
+
+Module Type: Bidder Adapter
+
+Maintainer: adplus.destek@yaani.com.tr
+
+# Description
+
+AdPlus Prebid.js Bidder Adapter. Only banner formats are supported.
+
+About us : https://ssp.ad-plus.com.tr/
+
+# Test Parameters
+
+```javascript
+var adUnits = [
+ {
+ code: "div-adplus",
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ ],
+ },
+ },
+ bids: [
+ {
+ bidder: "adplus",
+ params: {
+ inventoryId: "-1",
+ adUnitId: "-3",
+ },
+ },
+ ],
+ },
+];
+```
diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js
new file mode 100644
index 00000000000..5357c1a1ffd
--- /dev/null
+++ b/modules/adqueryIdSystem.js
@@ -0,0 +1,103 @@
+/**
+ * This module adds Adquery QID to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/adqueryIdSystem
+ * @requires module:modules/userId
+ */
+
+import {ajax} from '../src/ajax.js';
+import {getStorageManager} from '../src/storageManager.js';
+import {submodule} from '../src/hook.js';
+import * as utils from '../src/utils.js';
+
+const MODULE_NAME = 'qid';
+const AU_GVLID = 902;
+
+export const storage = getStorageManager(AU_GVLID, 'qid');
+
+/**
+ * Param or default.
+ * @param {String} param
+ * @param {String} defaultVal
+ */
+function paramOrDefault(param, defaultVal, arg) {
+ if (utils.isFn(param)) {
+ return param(arg);
+ } else if (utils.isStr(param)) {
+ return param;
+ }
+ return defaultVal;
+}
+
+/** @type {Submodule} */
+export const adqueryIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: MODULE_NAME,
+
+ /**
+ * IAB TCF Vendor ID
+ * @type {string}
+ */
+ gvlid: AU_GVLID,
+
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function
+ * @param {{value:string}} value
+ * @returns {{qid:Object}}
+ */
+ decode(value) {
+ let qid = storage.getDataFromLocalStorage('qid');
+ if (utils.isStr(qid)) {
+ return {qid: qid};
+ }
+ return (value && typeof value['qid'] === 'string') ? { 'qid': value['qid'] } : undefined;
+ },
+ /**
+ * performs action to obtain id and return a value in the callback's response argument
+ * @function
+ * @param {SubmoduleConfig} [config]
+ * @returns {IdResponse|undefined}
+ */
+ getId(config) {
+ if (!utils.isPlainObject(config.params)) {
+ config.params = {};
+ }
+ const url = paramOrDefault(config.params.url,
+ `https://bidder.adquery.io/prebid/qid`,
+ config.params.urlArg);
+
+ const resp = function (callback) {
+ let qid = storage.getDataFromLocalStorage('qid');
+ if (utils.isStr(qid)) {
+ const responseObj = {qid: qid};
+ callback(responseObj);
+ } else {
+ const callbacks = {
+ success: response => {
+ let responseObj;
+ if (response) {
+ try {
+ responseObj = JSON.parse(response);
+ } catch (error) {
+ utils.logError(error);
+ }
+ }
+ callback(responseObj);
+ },
+ error: error => {
+ utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
+ callback();
+ }
+ };
+ ajax(url, callbacks, undefined, {method: 'GET'});
+ }
+ };
+ return {callback: resp};
+ }
+};
+
+submodule('userId', adqueryIdSubmodule);
diff --git a/modules/adqueryIdSystem.md b/modules/adqueryIdSystem.md
new file mode 100644
index 00000000000..3a49ffbe4da
--- /dev/null
+++ b/modules/adqueryIdSystem.md
@@ -0,0 +1,35 @@
+# Adquery QID
+
+Adquery QID Module. For assistance setting up your module please contact us at [prebid@adquery.io](prebid@adquery.io).
+
+### Prebid Params
+
+Individual params may be set for the Adquery ID Submodule. At least one identifier must be set in the params.
+
+```
+pbjs.setConfig({
+ usersync: {
+ userIds: [{
+ name: 'qid',
+ storage: {
+ name: 'qid',
+ type: 'html5'
+ }
+ }]
+ }
+});
+```
+## Parameter Descriptions for the `usersync` Configuration Section
+The below parameters apply only to the Adquery User ID Module integration.
+
+| Param under usersync.userIds[] | Scope | Type | Description | Example |
+| --- | --- | --- | --- | --- |
+| name | Required | String | ID value for the Adquery ID module - `"qid"` | `"qid"` |
+| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. | |
+| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` |
+| storage.name | Required | String | The name of the html5 local storage where the user ID will be stored. | `"qid"` |
+| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` |
+| value | Optional | Object | Used only if the page has a separate mechanism for storing the Adquery ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"qid": "2abf9f001fcd81241b67"}` |
+| params | Optional | Object | Used to store params for the id system |
+| params.url | Optional | String | Set an alternate GET url for qid with this parameter |
+| params.urlArg | Optional | Object | Optional url parameter for params.url |
diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js
index 8523e01c0ea..44a9c90d438 100644
--- a/modules/adtelligentBidAdapter.js
+++ b/modules/adtelligentBidAdapter.js
@@ -20,6 +20,7 @@ const HOST_GETTERS = {
onefiftytwomedia: () => 'ghb.ads.152media.com',
mediafuse: () => 'ghb.hbmp.mediafuse.com',
bidsxchange: () => 'ghb.hbd.bidsxchange.com',
+ streamkey: () => 'ghb.hb.streamkey.net',
}
const getUri = function (bidderCode) {
let bidderWithoutSuffix = bidderCode.split('_')[0];
@@ -35,7 +36,7 @@ const syncsCache = {};
export const spec = {
code: BIDDER_CODE,
gvlid: 410,
- aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange',
+ aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey',
{ code: 'navelix', gvlid: 380 },
{
code: 'mediafuse',
diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js
index a02812a1608..81872100cd1 100644
--- a/modules/adxcgBidAdapter.js
+++ b/modules/adxcgBidAdapter.js
@@ -1,410 +1,362 @@
-import { logWarn, isStr, deepAccess, inIframe, checkCookieSupport, timestamp, getBidIdParameter, parseSizesInput, buildUrl, logMessage, isArray, deepSetValue, isPlainObject, triggerPixel, replaceAuctionPrice, isFn } from '../src/utils.js';
-import {config} from '../src/config.js'
-import {registerBidder} from '../src/adapters/bidderFactory.js'
-import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'
-import includes from 'core-js-pure/features/array/includes.js'
-
-/**
- * Adapter for requesting bids from adxcg.net
- * updated to latest prebid repo on 2017.10.20
- * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module
- * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters
- * updated to fix native support for image width/height and icon 2019.03.17
- * updated support for userid - pubcid,ttid 2019.05.28
- * updated to support prebid 3.0 - remove non https, move to banner.xx.sizes, remove utils.getTopWindowLocation,remove utils.getTopWindowUrl(),remove utils.getTopWindowReferrer()
- * updated to support prebid 4.0 - standardized video params, updated video validation, add onBidWon, onTimeOut, use standardized getFloor
- */
-
-const BIDDER_CODE = 'adxcg'
-const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]
-const SOURCE = 'pbjs10'
-const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playback_method', 'frameworks']
-const USER_PARAMS_AUCTION = ['forcedDspIds', 'forcedCampaignIds', 'forcedCreativeIds', 'gender', 'dnt', 'language']
-const USER_PARAMS_BID = ['lineparam1', 'lineparam2', 'lineparam3']
-const BIDADAPTERVERSION = 'r20210330PB40'
-const DEFAULT_MIN_FLOOR = 0;
+// jshint esversion: 6, es3: false, node: true
+'use strict';
+
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {NATIVE, BANNER, VIDEO} from '../src/mediaTypes.js';
+import {
+ mergeDeep,
+ _map,
+ deepAccess,
+ getDNT,
+ parseSizesInput,
+ deepSetValue,
+ isStr,
+ isArray,
+ isPlainObject,
+ parseUrl,
+ replaceAuctionPrice, triggerPixel
+} from '../src/utils.js';
+import {config} from '../src/config.js';
+
+const { getConfig } = config;
+
+const BIDDER_CODE = 'adxcg';
+const SECURE_BID_URL = 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1';
+
+const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' };
+const NATIVE_PARAMS = {
+ title: {
+ id: 0,
+ name: 'title'
+ },
+ icon: {
+ id: 2,
+ type: 1,
+ name: 'img'
+ },
+ image: {
+ id: 3,
+ type: 3,
+ name: 'img'
+ },
+ sponsoredBy: {
+ id: 5,
+ name: 'data',
+ type: 1
+ },
+ body: {
+ id: 4,
+ name: 'data',
+ type: 2
+ },
+ cta: {
+ id: 1,
+ type: 12,
+ name: 'data'
+ }
+};
export const spec = {
code: BIDDER_CODE,
- supportedMediaTypes: SUPPORTED_AD_TYPES,
-
- /**
- * Determines whether or not the given bid request is valid.
- *
- * @param {object} bid The bid params to validate.
- * @return boolean True if this is a valid bid, and false otherwise.
- */
- isBidRequestValid: function (bid) {
- if (!bid || !bid.params) {
- logWarn(BIDDER_CODE + ': Missing bid parameters');
- return false
- }
+ supportedMediaTypes: [ NATIVE, BANNER, VIDEO ],
+ isBidRequestValid: (bid) => {
+ const params = bid.params || {};
+ const { adzoneid } = params;
+ return !!(adzoneid);
+ },
+ buildRequests: (validBidRequests, bidderRequest) => {
+ let app, site;
- if (!isStr(bid.params.adzoneid)) {
- logWarn(BIDDER_CODE + ': adzoneid must be specified as a string');
- return false
- }
+ const commonFpd = getConfig('ortb2') || {};
+ let { user } = commonFpd;
+
+ if (typeof getConfig('app') === 'object') {
+ app = getConfig('app') || {};
+ if (commonFpd.app) {
+ mergeDeep(app, commonFpd.app);
+ }
+ } else {
+ site = getConfig('site') || {};
+ if (commonFpd.site) {
+ mergeDeep(site, commonFpd.site);
+ }
- if (isBannerRequest(bid)) {
- const banneroAdUnit = deepAccess(bid, 'mediaTypes.banner');
- if (!banneroAdUnit.sizes) {
- logWarn(BIDDER_CODE + ': banner sizes must be specified');
- return false;
+ if (!site.page) {
+ site.page = bidderRequest.refererInfo.referer;
+ site.domain = parseUrl(bidderRequest.refererInfo.referer).hostname;
}
}
- if (isVideoRequest(bid)) {
- // prebid 4.0 use standardized Video parameters
- const videoAdUnit = deepAccess(bid, 'mediaTypes.video');
+ const device = getConfig('device') || {};
+ device.w = device.w || window.innerWidth;
+ device.h = device.h || window.innerHeight;
+ device.ua = device.ua || navigator.userAgent;
+ device.dnt = getDNT() ? 1 : 0;
+ device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : '';
+
+ const tid = validBidRequests[0].transactionId;
+ const test = setOnAny(validBidRequests, 'params.test');
+ const currency = getConfig('currency.adServerCurrency');
+ const cur = currency && [ currency ];
+ const eids = setOnAny(validBidRequests, 'userIdAsEids');
+ const schain = setOnAny(validBidRequests, 'schain');
+
+ const imp = validBidRequests.map((bid, id) => {
+ const floorInfo = bid.getFloor ? bid.getFloor({
+ currency: currency || 'USD'
+ }) : {};
+ const bidfloor = floorInfo.floor;
+ const bidfloorcur = floorInfo.currency;
+ const { adzoneid } = bid.params;
+
+ const imp = {
+ id: id + 1,
+ tagid: adzoneid,
+ secure: 1,
+ bidfloor,
+ bidfloorcur,
+ ext: {
+ }
+ };
- if (!Array.isArray(videoAdUnit.playerSize)) {
- logWarn(BIDDER_CODE + ': video playerSize must be an array of integers');
- return false;
- }
+ const assets = _map(bid.nativeParams, (bidParams, key) => {
+ const props = NATIVE_PARAMS[key];
+ const asset = {
+ required: bidParams.required & 1,
+ };
+ if (props) {
+ asset.id = props.id;
+ let wmin, hmin, w, h;
+ let aRatios = bidParams.aspect_ratios;
+
+ if (aRatios && aRatios[0]) {
+ aRatios = aRatios[0];
+ wmin = aRatios.min_width || 0;
+ hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0;
+ }
- if (!videoAdUnit.context) {
- logWarn(BIDDER_CODE + ': video context must be specified');
- return false;
- }
+ if (bidParams.sizes) {
+ const sizes = flatten(bidParams.sizes);
+ w = sizes[0];
+ h = sizes[1];
+ }
- if (!Array.isArray(videoAdUnit.mimes) || videoAdUnit.mimes.length === 0) {
- logWarn(BIDDER_CODE + ': video mimes must be an array of strings');
- return false;
+ asset[props.name] = {
+ len: bidParams.len,
+ type: props.type,
+ wmin,
+ hmin,
+ w,
+ h
+ };
+
+ return asset;
+ }
+ }).filter(Boolean);
+
+ if (assets.length) {
+ imp.native = {
+ request: JSON.stringify({assets: assets})
+ };
}
- if (!Array.isArray(videoAdUnit.protocols) || videoAdUnit.protocols.length === 0) {
- logWarn(BIDDER_CODE + ': video protocols must be an array of integers');
- return false;
+ const bannerParams = deepAccess(bid, 'mediaTypes.banner');
+
+ if (bannerParams && bannerParams.sizes) {
+ const sizes = parseSizesInput(bannerParams.sizes);
+ const format = sizes.map(size => {
+ const [ width, height ] = size.split('x');
+ const w = parseInt(width, 10);
+ const h = parseInt(height, 10);
+ return { w, h };
+ });
+
+ imp.banner = {
+ format
+ };
}
- }
- return true
- },
+ const videoParams = deepAccess(bid, 'mediaTypes.video');
+ if (videoParams) {
+ imp.video = videoParams;
+ }
- /**
- * Make a server request from the list of BidRequests.
- *
- * an array of validBidRequests
- * Info describing the request to the server.
- */
- buildRequests: function (validBidRequests, bidderRequest) {
- let dt = new Date();
- let ratio = window.devicePixelRatio || 1;
- let iobavailable = window && window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && 'intersectionRatio' in window.IntersectionObserverEntry.prototype
-
- let bt = config.getConfig('bidderTimeout');
- if (window.PREBID_TIMEOUT) {
- bt = Math.min(window.PREBID_TIMEOUT, bt);
- }
+ return imp;
+ });
- let referrer = deepAccess(bidderRequest, 'refererInfo.referer');
- let page = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href');
-
- // add common parameters
- let beaconParams = {
- renderformat: 'javascript',
- ver: BIDADAPTERVERSION,
- secure: '1',
- source: SOURCE,
- uw: window.screen.width,
- uh: window.screen.height,
- dpr: ratio,
- bt: bt,
- isinframe: inIframe(),
- cookies: checkCookieSupport() ? '1' : '0',
- tz: dt.getTimezoneOffset(),
- dt: timestamp(),
- iob: iobavailable ? '1' : '0',
- pbjs: '$prebid.version$',
- rndid: Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000,
- ref: encodeURIComponent(referrer),
- url: encodeURIComponent(page)
+ const request = {
+ id: bidderRequest.auctionId,
+ site,
+ app,
+ user,
+ geo: { utcoffset: new Date().getTimezoneOffset() },
+ device,
+ source: { tid, fd: 1 },
+ ext: {
+ prebid: {
+ channel: {
+ name: 'pbjs',
+ version: '$prebid.version$'
+ }
+ }
+ },
+ cur,
+ imp
};
- if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
- beaconParams.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0';
- beaconParams.gdpr_consent = bidderRequest.gdprConsent.consentString;
- }
-
- if (isStr(deepAccess(validBidRequests, '0.userId.pubcid'))) {
- beaconParams.pubcid = validBidRequests[0].userId.pubcid;
+ if (test) {
+ request.is_debug = !!test;
+ request.test = 1;
}
-
- if (isStr(deepAccess(validBidRequests, '0.userId.tdid'))) {
- beaconParams.tdid = validBidRequests[0].userId.tdid;
+ if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) {
+ deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
+ deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1);
}
- if (isStr(deepAccess(validBidRequests, '0.userId.id5id.uid'))) {
- beaconParams.id5id = validBidRequests[0].userId.id5id.uid;
+ if (bidderRequest.uspConsent) {
+ deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent);
}
- if (isStr(deepAccess(validBidRequests, '0.userId.idl_env'))) {
- beaconParams.idl_env = validBidRequests[0].userId.idl_env;
+ if (eids) {
+ deepSetValue(request, 'user.ext.eids', eids);
}
- let biddercustom = config.getConfig(BIDDER_CODE);
- if (biddercustom) {
- Object.keys(biddercustom)
- .filter(param => includes(USER_PARAMS_AUCTION, param))
- .forEach(param => beaconParams[param] = encodeURIComponent(biddercustom[param]))
+ if (schain) {
+ deepSetValue(request, 'source.ext.schain', schain);
}
- // per impression parameters
- let adZoneIds = [];
- let prebidBidIds = [];
- let sizes = [];
- let bidfloors = [];
-
- validBidRequests.forEach((bid, index) => {
- adZoneIds.push(getBidIdParameter('adzoneid', bid.params));
- prebidBidIds.push(bid.bidId);
-
- let bidfloor = getFloor(bid);
- bidfloors.push(bidfloor);
-
- // copy all custom parameters impression level parameters not supported above
- let customBidParams = getBidIdParameter('custom', bid.params) || {}
- if (customBidParams) {
- Object.keys(customBidParams)
- .filter(param => includes(USER_PARAMS_BID, param))
- .forEach(param => beaconParams[param + '.' + index] = encodeURIComponent(customBidParams[param]))
- }
-
- if (isBannerRequest(bid)) {
- sizes.push(parseSizesInput(bid.mediaTypes.banner.sizes).join('|'));
- }
-
- if (isNativeRequest(bid)) {
- sizes.push('0x0');
- }
-
- if (isVideoRequest(bid)) {
- if (bid.params.video) {
- Object.keys(bid.params.video)
- .filter(param => includes(VIDEO_TARGETING, param))
- .forEach(param => beaconParams['video.' + param + '.' + index] = encodeURIComponent(bid.params.video[param]))
- }
- // copy video standarized params
- beaconParams['video.context' + '.' + index] = deepAccess(bid, 'mediaTypes.video.context');
- sizes.push(parseSizesInput(bid.mediaTypes.video.playerSize).join('|'));
- beaconParams['video.mimes' + '.' + index] = deepAccess(bid, 'mediaTypes.video.mimes').join(',');
- beaconParams['video.protocols' + '.' + index] = deepAccess(bid, 'mediaTypes.video.protocols').join(',');
- }
- })
-
- beaconParams.adzoneid = adZoneIds.join(',');
- beaconParams.format = sizes.join(',');
- beaconParams.prebidBidIds = prebidBidIds.join(',');
- beaconParams.bidfloors = bidfloors.join(',');
-
- let adxcgRequestUrl = buildUrl({
- protocol: 'https',
- hostname: 'hbps.adxcg.net',
- pathname: '/get/adi',
- search: beaconParams
- });
-
- logMessage(`calling adi adxcg`);
return {
- contentType: 'text/plain',
- method: 'GET',
- url: adxcgRequestUrl,
- withCredentials: true
+ method: 'POST',
+ url: SECURE_BID_URL,
+ data: JSON.stringify(request),
+ options: {
+ contentType: 'application/json'
+ },
+ bids: validBidRequests
};
},
- /**
- * Unpack the response from the server into a list of bids.
- *
- * @param {*} serverResponse A successful response from the server.
- * @return {bidRequests[]} An array of bids which were nested inside the server.
- */
- interpretResponse:
- function (serverResponse) {
- logMessage(`interpretResponse adxcg`);
- let bidsAll = [];
-
- if (!serverResponse || !serverResponse.body || !isArray(serverResponse.body.seatbid) || !serverResponse.body.seatbid.length) {
- logWarn(BIDDER_CODE + ': empty bid response');
- return bidsAll;
- }
-
- serverResponse.body.seatbid.forEach((bids) => {
- bids.bid.forEach((serverResponseOneItem) => {
- let bid = {}
- // parse general fields
- bid.requestId = serverResponseOneItem.impid;
- bid.cpm = serverResponseOneItem.price;
- bid.creativeId = parseInt(serverResponseOneItem.crid);
- bid.currency = serverResponseOneItem.currency ? serverResponseOneItem.currency : 'USD';
- bid.netRevenue = serverResponseOneItem.netRevenue ? serverResponseOneItem.netRevenue : true;
- bid.ttl = serverResponseOneItem.ttl ? serverResponseOneItem.ttl : 300;
- bid.width = serverResponseOneItem.w;
- bid.height = serverResponseOneItem.h;
- bid.burl = serverResponseOneItem.burl || '';
-
- if (serverResponseOneItem.dealid != null && serverResponseOneItem.dealid.trim().length > 0) {
- bid.dealId = serverResponseOneItem.dealid;
- }
+ interpretResponse: function(serverResponse, { bids }) {
+ if (!serverResponse.body) {
+ return;
+ }
+ const { seatbid, cur } = serverResponse.body;
+
+ const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => {
+ result[bid.impid - 1] = bid;
+ return result;
+ }, []);
+
+ return bids.map((bid, id) => {
+ const bidResponse = bidResponses[id];
+ if (bidResponse) {
+ const mediaType = deepAccess(bidResponse, 'ext.crType');
+ const result = {
+ requestId: bid.bidId,
+ cpm: bidResponse.price,
+ creativeId: bidResponse.crid,
+ ttl: bidResponse.ttl ? bidResponse.ttl : 300,
+ netRevenue: bid.netRevenue === 'net',
+ currency: cur,
+ burl: bid.burl || '',
+ mediaType: mediaType,
+ width: bidResponse.w,
+ height: bidResponse.h,
+ dealId: bidResponse.dealid,
+ };
+
+ deepSetValue(result, 'meta.mediaType', mediaType);
+ if (isArray(bidResponse.adomain)) {
+ deepSetValue(result, 'meta.advertiserDomains', bidResponse.adomain);
+ }
- if (serverResponseOneItem.ext.crType === 'banner') {
- bid.ad = serverResponseOneItem.adm;
- } else if (serverResponseOneItem.ext.crType === 'video') {
- bid.vastUrl = serverResponseOneItem.nurl;
- bid.vastXml = serverResponseOneItem.adm;
- bid.mediaType = 'video';
- } else if (serverResponseOneItem.ext.crType === 'native') {
- bid.mediaType = 'native';
- bid.native = parseNative(JSON.parse(serverResponseOneItem.adm));
- } else {
- logWarn(BIDDER_CODE + ': unknown or undefined crType');
+ if (isPlainObject(bidResponse.ext)) {
+ if (isStr(bidResponse.ext.mediaType)) {
+ deepSetValue(result, 'meta.mediaType', mediaType);
}
-
- // prebid 4.0 meta taxonomy
- if (isArray(serverResponseOneItem.adomain)) {
- deepSetValue(bid, 'meta.advertiserDomains', serverResponseOneItem.adomain);
+ if (isStr(bidResponse.ext.advertiser_id)) {
+ deepSetValue(result, 'meta.advertiserId', bidResponse.ext.advertiser_id);
}
- if (isArray(serverResponseOneItem.cat)) {
- deepSetValue(bid, 'meta.secondaryCatIds', serverResponseOneItem.cat);
+ if (isStr(bidResponse.ext.advertiser_name)) {
+ deepSetValue(result, 'meta.advertiserName', bidResponse.ext.advertiser_name);
}
- if (isPlainObject(serverResponseOneItem.ext)) {
- if (isStr(serverResponseOneItem.ext.advertiser_id)) {
- deepSetValue(bid, 'meta.mediaType', serverResponseOneItem.ext.mediaType);
- }
- if (isStr(serverResponseOneItem.ext.advertiser_id)) {
- deepSetValue(bid, 'meta.advertiserId', serverResponseOneItem.ext.advertiser_id);
- }
- if (isStr(serverResponseOneItem.ext.advertiser_name)) {
- deepSetValue(bid, 'meta.advertiserName', serverResponseOneItem.ext.advertiser_name);
- }
- if (isStr(serverResponseOneItem.ext.agency_name)) {
- deepSetValue(bid, 'meta.agencyName', serverResponseOneItem.ext.agency_name);
- }
+ if (isStr(bidResponse.ext.agency_name)) {
+ deepSetValue(result, 'meta.agencyName', bidResponse.ext.agency_name);
}
- bidsAll.push(bid)
- })
- })
- return bidsAll
- },
+ }
+ if (mediaType === BANNER) {
+ result.ad = bidResponse.adm;
+ } else if (mediaType === NATIVE) {
+ result.native = parseNative(bidResponse);
+ result.width = 0;
+ result.height = 0;
+ } else if (mediaType === VIDEO) {
+ result.vastUrl = bidResponse.nurl;
+ result.vastXml = bidResponse.adm;
+ }
- onBidWon: (bid) => {
- if (bid.burl) {
- triggerPixel(replaceAuctionPrice(bid.burl, bid.originalCpm));
- }
+ return result;
+ }
+ }).filter(Boolean);
},
+ getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => {
+ const syncs = [];
+ let syncUrl = config.getConfig('adxcg.usersyncUrl');
+
+ let query = [];
+ if (syncOptions.pixelEnabled && syncUrl) {
+ if (gdprConsent) {
+ query.push('gdpr=' + (gdprConsent.gdprApplies & 1));
+ query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''));
+ }
+ if (uspConsent) {
+ query.push('us_privacy=' + encodeURIComponent(uspConsent));
+ }
- onTimeout(timeoutData) {
- if (timeoutData == null) {
- return;
+ syncs.push({
+ type: 'image',
+ url: syncUrl + (query.length ? '?' + query.join('&') : '')
+ });
}
-
- let beaconParams = {
- A: timeoutData.bidder,
- bid: timeoutData.bidId,
- a: timeoutData.adUnitCode,
- cn: timeoutData.timeout,
- aud: timeoutData.auctionId,
- };
- let adxcgRequestUrl = buildUrl({
- protocol: 'https',
- hostname: 'hbps.adxcg.net',
- pathname: '/event/timeout.gif',
- search: beaconParams
- });
- logWarn(BIDDER_CODE + ': onTimeout called');
- triggerPixel(adxcgRequestUrl);
+ return syncs;
},
-
- getUserSyncs: function (syncOptions, serverResponses, gdprConsent) {
- let params = '';
- if (gdprConsent && 'gdprApplies' in gdprConsent) {
- if (gdprConsent.consentString) {
- if (typeof gdprConsent.gdprApplies === 'boolean') {
- params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
- } else {
- params += `?gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
- }
- }
- }
-
- if (syncOptions.iframeEnabled) {
- return [{
- type: 'iframe',
- url: 'https://cdn.adxcg.net/pb-sync.html' + params
- }];
+ onBidWon: (bid) => {
+ // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server
+ // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute
+ if (bid.nurl) {
+ triggerPixel(replaceAuctionPrice(bid.nurl, bid.originalCpm))
}
}
-}
+};
-function isVideoRequest(bid) {
- return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video');
-}
+registerBidder(spec);
-function isBannerRequest(bid) {
- return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner');
-}
-
-function isNativeRequest(bid) {
- return bid.mediaType === 'native' || !!deepAccess(bid, 'mediaTypes.native');
-}
-
-function getFloor(bid) {
- if (!isFn(bid.getFloor)) {
- return deepAccess(bid, 'params.floor', DEFAULT_MIN_FLOOR);
- }
-
- try {
- const floor = bid.getFloor({
- currency: 'EUR',
- mediaType: '*',
- size: '*',
- bidRequest: bid
- });
- return floor.floor;
- } catch (e) {
- logWarn(BIDDER_CODE + ': call to getFloor failed:' + e.message);
- return DEFAULT_MIN_FLOOR;
- }
-}
-
-function parseNative(nativeResponse) {
- let bidNative = {};
- bidNative = {
- clickUrl: nativeResponse.link.url,
- impressionTrackers: nativeResponse.imptrackers,
- clickTrackers: nativeResponse.clktrackers,
- javascriptTrackers: nativeResponse.jstrackers
+function parseNative(bid) {
+ const { assets, link, imptrackers, jstracker } = JSON.parse(bid.adm);
+ const result = {
+ clickUrl: link.url,
+ clickTrackers: link.clicktrackers || undefined,
+ impressionTrackers: imptrackers || undefined,
+ javascriptTrackers: jstracker ? [ jstracker ] : undefined
};
-
- nativeResponse.assets.forEach(asset => {
- if (asset.title && asset.title.text) {
- bidNative.title = asset.title.text;
- }
-
- if (asset.img && asset.img.url) {
- bidNative.image = {
- url: asset.img.url,
- height: asset.img.h,
- width: asset.img.w
- };
- }
-
- if (asset.icon && asset.icon.url) {
- bidNative.icon = {
- url: asset.icon.url,
- height: asset.icon.h,
- width: asset.icon.w
- };
- }
-
- if (asset.data && asset.data.label === 'DESC' && asset.data.value) {
- bidNative.body = asset.data.value;
+ assets.forEach(asset => {
+ const kind = NATIVE_ASSET_IDS[asset.id];
+ const content = kind && asset[NATIVE_PARAMS[kind].name];
+ if (content) {
+ result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h };
}
+ });
+ return result;
+}
- if (asset.data && asset.data.label === 'SPONSORED' && asset.data.value) {
- bidNative.sponsoredBy = asset.data.value;
+function setOnAny(collection, key) {
+ for (let i = 0, result; i < collection.length; i++) {
+ result = deepAccess(collection[i], key);
+ if (result) {
+ return result;
}
- })
- return bidNative;
+ }
}
-registerBidder(spec)
+function flatten(arr) {
+ return [].concat(...arr);
+}
diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md
index 8eccdb11dee..1e4ef9cd6f9 100644
--- a/modules/adxcgBidAdapter.md
+++ b/modules/adxcgBidAdapter.md
@@ -34,31 +34,34 @@ Module that connects to an Adxcg.com zone to fetch bids.
code: 'native-ad-div',
mediaTypes: {
native: {
- image: {
+ image: {
sendId: false,
- required: true,
- sizes: [80, 80]
+ required: false,
+ sizes: [127, 83]
},
icon: {
- sendId: true,
- },
- brand: {
+ sizes: [80, 80],
+ required: false,
sendId: true,
},
title: {
sendId: false,
- required: true,
+ required: false,
len: 75
},
body: {
sendId: false,
- required: true,
+ required: false,
len: 200
},
- sponsoredBy: {
+ cta: {
sendId: false,
required: false,
- len: 20
+ len: 75
+ },
+ sponsoredBy: {
+ sendId: false,
+ required: false
}
}
},
@@ -73,21 +76,19 @@ Module that connects to an Adxcg.com zone to fetch bids.
code: 'video-div',
mediaTypes: {
video: {
- playerSize: [640, 480],
- context: 'instream',
- mimes: ['video/mp4'],
- protocols: [5, 6, 8],
- playback_method: ['auto_play_sound_off']
- }
+ playerSize: [640, 480],
+ context: 'instream',
+ mimes: ['video/mp4'],
+ protocols: [2, 3, 5, 6, 8],
+ playback_method: ['auto_play_sound_off'],
+ maxduration: 100,
+ skip: 1
+ }
},
bids: [{
bidder: 'adxcg',
params: {
- adzoneid: '20',
- video: {
- maxduration: 100,
- skippable: true
- }
+ adzoneid: '20'
}
}]
}
diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js
index 334309aec5c..155e8ca3c7a 100644
--- a/modules/adyoulikeBidAdapter.js
+++ b/modules/adyoulikeBidAdapter.js
@@ -1,6 +1,7 @@
import { deepAccess, buildUrl, parseSizesInput } from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
+import { createEidsArray } from './userId/eids.js';
import find from 'core-js-pure/features/array/find.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
@@ -97,17 +98,21 @@ export const spec = {
PageRefreshed: getPageRefreshed()
};
- if (bidderRequest && bidderRequest.gdprConsent) {
+ if (bidderRequest.gdprConsent) {
payload.gdprConsent = {
consentString: bidderRequest.gdprConsent.consentString,
consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : null
};
}
- if (bidderRequest && bidderRequest.uspConsent) {
+ if (bidderRequest.uspConsent) {
payload.uspConsent = bidderRequest.uspConsent;
}
+ if (deepAccess(bidderRequest, 'userId')) {
+ payload.userId = createEidsArray(bidderRequest.userId);
+ }
+
const data = JSON.stringify(payload);
const options = {
withCredentials: true
@@ -175,11 +180,13 @@ function getCanonicalUrl() {
/* Get mediatype from bidRequest */
function getMediatype(bidRequest) {
+ if (deepAccess(bidRequest, 'mediaTypes.banner')) {
+ return BANNER;
+ }
if (deepAccess(bidRequest, 'mediaTypes.video')) {
return VIDEO;
- } else if (deepAccess(bidRequest, 'mediaTypes.banner')) {
- return BANNER;
- } else if (deepAccess(bidRequest, 'mediaTypes.native')) {
+ }
+ if (deepAccess(bidRequest, 'mediaTypes.native')) {
return NATIVE;
}
}
@@ -340,7 +347,7 @@ function getTrackers(eventsArray, jsTrackers) {
function getVideoAd(response) {
var adJson = {};
- if (typeof response.Ad === 'string') {
+ if (typeof response.Ad === 'string' && response.Ad.indexOf('\/\*PREBID\*\/') > 0) {
adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]);
return deepAccess(adJson, 'Content.MainVideo.Vast');
}
@@ -473,13 +480,15 @@ function createBid(response, bidRequests) {
meta: response.Meta || { advertiserDomains: [] }
};
- if (request && request.Native) {
+ // retreive video response if present
+ const vast64 = response.Vast || getVideoAd(response);
+ if (vast64) {
+ bid.vastXml = window.atob(vast64);
+ bid.mediaType = 'video';
+ } else if (request.Native) {
+ // format Native response if Native was requested
bid.native = getNativeAssets(response, request.Native);
bid.mediaType = 'native';
- } else if (request && request.Video) {
- const vast64 = response.Vast || getVideoAd(response);
- bid.vastXml = vast64 ? window.atob(vast64) : '';
- bid.mediaType = 'video';
} else {
bid.width = response.Width;
bid.height = response.Height;
diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js
index 8d212204da8..f5403cca3eb 100644
--- a/modules/airgridRtdProvider.js
+++ b/modules/airgridRtdProvider.js
@@ -33,7 +33,7 @@ export function attachScriptTagToDOM(rtdConfig) {
edktInitializor.load = function(e) {
var p = e || 'sdk';
var n = document.createElement('script');
- n.type = 'text/javascript';
+ n.type = 'module';
n.async = true;
n.src = 'https://cdn.edkt.io/' + p + '/edgekit.min.js';
document.getElementsByTagName('head')[0].appendChild(n);
diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js
new file mode 100644
index 00000000000..d143a53fbf4
--- /dev/null
+++ b/modules/akamaiDapRtdProvider.js
@@ -0,0 +1,474 @@
+/**
+ * This module adds the Akamai DAP RTD provider to the real time data module
+ * The {@link module:modules/realTimeData} module is required
+ * The module will fetch real-time data from DAP
+ * @module modules/akamaiDapRtdProvider
+ * @requires module:modules/realTimeData
+ */
+import {ajax} from '../src/ajax.js';
+import {config} from '../src/config.js';
+import {getStorageManager} from '../src/storageManager.js';
+import {submodule} from '../src/hook.js';
+import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js';
+
+const MODULE_NAME = 'realTimeData';
+const SUBMODULE_NAME = 'dap';
+
+export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments';
+export const storage = getStorageManager(null, SUBMODULE_NAME);
+
+/**
+ * Lazy merge objects.
+ * @param {String} target
+ * @param {String} source
+ */
+function mergeLazy(target, source) {
+ if (!isPlainObject(target)) {
+ target = {};
+ }
+ if (!isPlainObject(source)) {
+ source = {};
+ }
+ return mergeDeep(target, source);
+}
+
+/**
+ * Add real-time data & merge segments.
+ * @param {Object} bidConfig
+ * @param {Object} rtd
+ * @param {Object} rtdConfig
+ */
+export function addRealTimeData(rtd) {
+ logInfo('DEBUG(addRealTimeData) - ENTER');
+ if (isPlainObject(rtd.ortb2)) {
+ let ortb2 = config.getConfig('ortb2') || {};
+ logMessage('DEBUG(addRealTimeData): merging original: ', ortb2);
+ logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2);
+ config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)});
+ }
+ logInfo('DEBUG(addRealTimeData) - EXIT');
+}
+
+/**
+ * Real-time data retrieval from Audigent
+ * @param {Object} reqBidsConfigObj
+ * @param {function} onDone
+ * @param {Object} rtdConfi
+ * @param {Object} userConsent
+ */
+export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
+ logInfo('DEBUG(getRealTimeData) - ENTER');
+ logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname);
+ logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion);
+ let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY);
+ if (jsonData) {
+ let data = JSON.parse(jsonData);
+ if (data.rtd) {
+ addRealTimeData(data.rtd);
+ onDone();
+ logInfo('DEBUG(getRealTimeData) - 1');
+ // Don't return - ensure the data is always fresh.
+ }
+ }
+
+ if (rtdConfig && isPlainObject(rtdConfig.params)) {
+ let config = {
+ api_hostname: rtdConfig.params.apiHostname,
+ api_version: rtdConfig.params.apiVersion,
+ domain: rtdConfig.params.domain,
+ segtax: rtdConfig.params.segtax
+ };
+ let identity = {
+ type: rtdConfig.params.identityType
+ };
+ let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl);
+ if (token !== null) {
+ let membership = dapUtils.dapGetMembership(config, token);
+ let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config);
+ logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment);
+ let data = {
+ rtd: {
+ ortb2: {
+ user: {
+ data: [
+ udSegment
+ ]
+ },
+ site: {
+ ext: {
+ data: {
+ dapSAID: membership.said
+ }
+ }
+ }
+ }
+ }
+ };
+ storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data));
+ onDone();
+ }
+ }
+}
+
+/**
+ * Module init
+ * @param {Object} provider
+ * @param {Object} userConsent
+ * @return {boolean}
+ */
+function init(provider, userConsent) {
+ return true;
+}
+
+/** @type {RtdSubmodule} */
+export const akamaiDapRtdSubmodule = {
+ name: SUBMODULE_NAME,
+ getBidRequestData: getRealTimeData,
+ init: init
+};
+
+submodule(MODULE_NAME, akamaiDapRtdSubmodule);
+
+export const dapUtils = {
+
+ dapGetToken: function(config, identity, ttl) {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let storageName = 'async_dap_token';
+ let token = null;
+
+ if (ttl == 0) {
+ localStorage.removeItem(storageName);
+ }
+
+ let item = JSON.parse(localStorage.getItem(storageName));
+ if (item == null) {
+ item = {
+ expires_at: now - 1,
+ token: null
+ };
+ } else {
+ token = item.token;
+ }
+
+ if (now > item.expires_at) {
+ dapUtils.dapLog('Token missing or expired, fetching a new one...');
+ // Trigger a refresh
+ let configAsync = {...config};
+ dapUtils.dapTokenize(configAsync, identity,
+ function(token, status, xhr) {
+ item.expires_at = now + ttl;
+ item.token = token;
+ localStorage.setItem(storageName, JSON.stringify(item));
+ dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds');
+ let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100');
+ if (deviceId100 != null) {
+ localStorage.setItem('dap_deviceId100', deviceId100);
+ dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100);
+ }
+ },
+ function(xhr, status, error) {
+ logError('ERROR(' + error + '): failed to retrieve token! ' + status);
+ }
+ );
+ }
+
+ return token;
+ },
+
+ dapGetMembership: function(config, token) {
+ let now = Math.round(Date.now() / 1000.0); // in seconds
+ let storageName = 'async_dap_membership';
+ let maxTtl = 3600; // if the cached membership is older than this, return null
+ let membership = null;
+ let item = JSON.parse(localStorage.getItem(storageName));
+ if (item == null || (now - item.expires_at) > maxTtl) {
+ item = {
+ expires_at: now - 1,
+ said: null,
+ cohorts: null,
+ attributes: null
+ };
+ } else {
+ membership = {
+ said: item.said,
+ cohorts: item.cohorts,
+ attributes: null
+ };
+ }
+
+ // Always refresh the cached membership.
+ let configAsync = {...config};
+ dapUtils.dapMembership(configAsync, token,
+ function(membership, status, xhr) {
+ item.expires_at = now + maxTtl;
+ item.said = membership.said;
+ item.cohorts = membership.cohorts;
+ localStorage.setItem(storageName, JSON.stringify(item));
+ dapUtils.dapLog('Successfully updated and stored membership:');
+ dapUtils.dapLog(item);
+ },
+ function(xhr, status, error) {
+ logError('ERROR(' + error + '): failed to retrieve membership! ' + status);
+ }
+ );
+
+ return membership;
+ },
+
+ /**
+ * DESCRIPTION
+ *
+ * Convert a DAP membership response to an OpenRTB2 segment object suitable
+ * for insertion into user.data.segment or site.data.segment.
+ */
+ dapMembershipToRtbSegment: function(membership, config) {
+ let segment = {
+ name: 'dap.akamai.com',
+ ext: {
+ 'segtax': config.segtax
+ },
+ segment: []
+ };
+ if (membership != null) {
+ for (const i of membership.cohorts) {
+ segment.segment.push({ id: i });
+ }
+ }
+ return segment;
+ },
+
+ dapLog: function(args) {
+ let css = '';
+ css += 'display: inline-block;';
+ css += 'color: #fff;';
+ css += 'background: #F28B20;';
+ css += 'padding: 1px 4px;';
+ css += 'border-radius: 3px';
+
+ logInfo('%cDAP Client', css, args);
+ },
+
+ /*******************************************************************************
+ *
+ * V2 (And Beyond) API
+ *
+ ******************************************************************************/
+
+ /**
+ * SYNOPSIS
+ *
+ * dapTokenize( config, identity );
+ *
+ * DESCRIPTION
+ *
+ * Tokenize an identity into a secure, privacy safe pseudonymiziation.
+ *
+ * PARAMETERS
+ *
+ * config: an array of system configuration parameters
+ *
+ * identity: an array of identity parameters passed to the tokenizing system
+ *
+ * EXAMPLE
+ *
+ * config = {
+ * api_hostname: "prebid.dap.akadns.net", // required
+ * domain: "prebid.org", // required
+ * api_version: "x1", // optional, default "x1"
+ * };
+ *
+ * token = null;
+ * identity_email = {
+ * type: "email",
+ * identity: "obiwan@jedi.com"
+ * attributes: { cohorts: [ "100:1641013200", "101:1641013200", "102":3:1641013200" ] },
+ * };
+ * dap_x1_tokenize( config, identity_email,
+ * function( response, status, xhr ) { token = response; },
+ * function( xhr, status, error ) { ; } // handle error
+ *
+ * token = null;
+ * identity_signature = { type: "signature:1.0.0" };
+ * dap_x1_tokenize( config, identity_signature,
+ * function( response, status, xhr } { token = response; },
+ * function( xhr, status, error ) { ; } // handle error
+ */
+ dapTokenize: function(config, identity, onSuccess = null, onError = null) {
+ if (onError == null) {
+ onError = function(xhr, status, error) {};
+ }
+
+ if (config == null || typeof (config) == typeof (undefined)) {
+ onError(null, 'Invalid config object', 'ClientError');
+ return;
+ }
+
+ if (typeof (config.domain) != 'string') {
+ onError(null, 'Invalid config.domain: must be a string', 'ClientError');
+ return;
+ }
+
+ if (config.domain.length <= 0) {
+ onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError');
+ return;
+ }
+
+ if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) {
+ config.api_version = 'x1';
+ }
+
+ if (typeof (config.api_version) != 'string') {
+ onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError');
+ return;
+ }
+
+ if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
+ onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError');
+ return;
+ }
+
+ if (identity == null || typeof (identity) == typeof (undefined)) {
+ onError(null, 'Invalid identity object', 'ClientError');
+ return;
+ }
+
+ if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) {
+ onError(null, "Identity must contain a valid 'type' field", 'ClientError');
+ return;
+ }
+
+ let apiParams = {
+ 'type': identity.type,
+ };
+
+ if (typeof (identity.identity) != typeof (undefined)) {
+ apiParams.identity = identity.identity;
+ }
+ if (typeof (identity.attributes) != typeof (undefined)) {
+ apiParams.attributes = identity.attributes;
+ }
+
+ let method;
+ let body;
+ let path;
+ switch (config.api_version) {
+ case 'x1':
+ case 'x1-dev':
+ method = 'POST';
+ path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize';
+ body = JSON.stringify(apiParams);
+ break;
+ default:
+ onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError');
+ return;
+ }
+
+ let url = 'https://' + config.api_hostname + path;
+ let cb = {
+ success: (response, request) => {
+ let token = null;
+ switch (config.api_version) {
+ case 'x1':
+ case 'x1-dev':
+ token = request.getResponseHeader('Akamai-DAP-Token');
+ break;
+ }
+ onSuccess(token, request.status, request);
+ },
+ error: (request, error) => {
+ onError(request, request.statusText, error);
+ }
+ };
+
+ ajax(url, cb, body, {
+ method: method,
+ customHeaders: {
+ 'Content-Type': 'application/json',
+ 'Pragma': 'akamai-x-cache-on'
+ }
+ });
+ },
+
+ /**
+ * SYNOPSIS
+ *
+ * dapMembership( config, token, onSuccess, onError );
+ *
+ * DESCRIPTION
+ *
+ * Return the audience segment membership along with a new Secure Advertising
+ * ID for this token.
+ *
+ * PARAMETERS
+ *
+ * config: an array of system configuration parameters
+ *
+ * token: the token previously returned from the tokenize API
+ *
+ * EXAMPLE
+ *
+ * config = {
+ * api_hostname: 'api.dap.akadns.net',
+ * };
+ *
+ * // token from dap_x1_tokenize
+ *
+ * dapMembership( config, token,
+ * function( membership, status, xhr ) {
+ * // Run auction with membership.segments and membership.said
+ * },
+ * function( xhr, status, error ) {
+ * // error
+ * } );
+ *
+ */
+ dapMembership: function(config, token, onSuccess = null, onError = null) {
+ if (onError == null) {
+ onError = function(xhr, status, error) {};
+ }
+
+ if (config == null || typeof (config) == typeof (undefined)) {
+ onError(null, 'Invalid config object', 'ClientError');
+ return;
+ }
+
+ if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) {
+ config.api_version = 'x1';
+ }
+
+ if (typeof (config.api_version) != 'string') {
+ onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError');
+ return;
+ }
+
+ if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) {
+ onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError');
+ return;
+ }
+
+ if (token == null || typeof (token) != 'string') {
+ onError(null, 'Invalid token: must be a non-null string', 'ClientError');
+ return;
+ }
+ let path = '/data-activation/' +
+ config.api_version +
+ '/token/' + token +
+ '/membership';
+
+ let url = 'https://' + config.api_hostname + path;
+
+ let cb = {
+ success: (response, request) => {
+ onSuccess(JSON.parse(response), request.status, request);
+ },
+ error: (error, request) => {
+ onError(request, request.status, error);
+ }
+ };
+
+ ajax(url, cb, undefined, {
+ method: 'GET',
+ customHeaders: {}
+ });
+ }
+}
diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md
new file mode 100644
index 00000000000..ade11b88602
--- /dev/null
+++ b/modules/akamaiDapRtdProvider.md
@@ -0,0 +1,47 @@
+### Overview
+
+ Akamai DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the SAID to the bid-stream.
+
+### Integration
+
+ 1) Build the akamaiDapRTD module into the Prebid.js package with:
+
+ ```
+ gulp build --modules=akamaiDapRtdProvider,...
+ ```
+
+ 2) Use `setConfig` to instruct Prebid.js to initilaize the akamaiDapRtdProvider module, as specified below.
+
+### Configuration
+
+```
+ pbjs.setConfig({
+ realTimeData: {
+ dataProviders: [
+ {
+ name: "dap",
+ waitForIt: true,
+ params: {
+ apiHostname: '',
+ apiVersion: "x1",
+ domain: 'your-domain.com',
+ identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0',
+ segtax: ,
+ tokenTtl: 5,
+ }
+ }
+ ]
+ }
+ });
+ ```
+
+Please reach out to your Akamai account representative(Prebid@akamai.com) to get provisioned on the DAP platform.
+
+
+### Testing
+To view an example of available segments returned by dap:
+```
+‘gulp serve --modules=rtdModule,akamaiDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter’
+```
+and then point your browser at:
+"http://localhost:9999/integrationExamples/gpt/akamaidap_segments_example.html"
diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js
index 96bdf153e3f..53249e92a77 100644
--- a/modules/aniviewBidAdapter.js
+++ b/modules/aniviewBidAdapter.js
@@ -309,7 +309,7 @@ function getUserSyncs(syncOptions, serverResponses) {
export const spec = {
code: BIDDER_CODE,
gvlid: GVLID,
- aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo'],
+ aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo'],
supportedMediaTypes: [VIDEO, BANNER],
isBidRequestValid,
buildRequests,
diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js
index 7203439059b..c9f64ab66b0 100644
--- a/modules/aolBidAdapter.js
+++ b/modules/aolBidAdapter.js
@@ -38,7 +38,8 @@ const SUPPORTED_USER_ID_SOURCES = [
'liveintent.com',
'quantcast.com',
'verizonmedia.com',
- 'liveramp.com'
+ 'liveramp.com',
+ 'yahoo.com'
];
const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`;
diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js
index 7dcbd74d779..008a3f7287d 100644
--- a/modules/appnexusBidAdapter.js
+++ b/modules/appnexusBidAdapter.js
@@ -1,4 +1,4 @@
-import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, logError, logInfo, deepAccess, logMessage, convertTypes, isStr, getParameterByName, deepClone, chunk, logWarn, getBidRequest, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, getMaxValueFromArray, fill, getMinValueFromArray, isArrayOfNums, isFn } from '../src/utils.js';
+import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, logError, logInfo, deepAccess, logMessage, convertTypes, isStr, getParameterByName, deepClone, chunk, logWarn, getBidRequest, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, getMaxValueFromArray, fill, getMinValueFromArray, isArrayOfNums, isFn, isAllowZeroCpmBidsEnabled } from '../src/utils.js';
import { Renderer } from '../src/Renderer.js';
import { config } from '../src/config.js';
import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js';
@@ -78,7 +78,6 @@ export const spec = {
{ code: 'districtm', gvlid: 144 },
{ code: 'adasta' },
{ code: 'beintoo', gvlid: 618 },
- { code: 'targetVideo' },
],
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
@@ -202,6 +201,17 @@ export const spec = {
payload.app = appIdObj;
}
+ let auctionKeywords = config.getConfig('appnexusAuctionKeywords');
+ if (isPlainObject(auctionKeywords)) {
+ let aucKeywords = transformBidderParamKeywords(auctionKeywords);
+
+ if (aucKeywords.length > 0) {
+ aucKeywords.forEach(deleteValues);
+ }
+
+ payload.keywords = aucKeywords;
+ }
+
if (config.getConfig('adpod.brandCategoryExclusion')) {
payload.brand_category_uniqueness = true;
}
@@ -293,7 +303,8 @@ export const spec = {
serverResponse.tags.forEach(serverBid => {
const rtbBid = getRtbBid(serverBid);
if (rtbBid) {
- if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) {
+ const cpmCheck = (isAllowZeroCpmBidsEnabled(bidderRequest.bidderCode)) ? rtbBid.cpm >= 0 : rtbBid.cpm > 0;
+ if (cpmCheck && includes(this.supportedMediaTypes, rtbBid.ad_type)) {
const bid = newBid(serverBid, rtbBid, bidderRequest);
bid.mediaType = parseMediaType(rtbBid);
bids.push(bid);
@@ -598,6 +609,26 @@ function newBid(serverBid, rtbBid, bidderRequest) {
bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id });
}
+ // temporary function; may remove at later date if/when adserver fully supports dchain
+ function setupDChain(rtbBid) {
+ let dchain = {
+ ver: '1.0',
+ complete: 0,
+ nodes: [{
+ bsid: rtbBid.buyer_member_id.toString()
+ }],
+ };
+
+ return dchain;
+ }
+ if (rtbBid.buyer_member_id) {
+ bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)});
+ }
+
+ if (rtbBid.brand_id) {
+ bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id });
+ }
+
if (rtbBid.rtb.video) {
// shared video properties used for all 3 contexts
Object.assign(bid, {
@@ -696,9 +727,11 @@ function newBid(serverBid, rtbBid, bidderRequest) {
});
try {
if (rtbBid.rtb.trackers) {
- const url = rtbBid.rtb.trackers[0].impression_urls[0];
- const tracker = createTrackPixelHtml(url);
- bid.ad += tracker;
+ for (let i = 0; i < rtbBid.rtb.trackers[0].impression_urls.length; i++) {
+ const url = rtbBid.rtb.trackers[0].impression_urls[i];
+ const tracker = createTrackPixelHtml(url);
+ bid.ad += tracker;
+ }
}
} catch (error) {
logError('Error appending tracking pixel', error);
diff --git a/modules/asealBidAdapter.js b/modules/asealBidAdapter.js
new file mode 100644
index 00000000000..559afefa94b
--- /dev/null
+++ b/modules/asealBidAdapter.js
@@ -0,0 +1,58 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
+
+export const BIDDER_CODE = 'aseal';
+const SUPPORTED_AD_TYPES = [BANNER];
+export const API_ENDPOINT = 'https://tkprebid.aotter.net/prebid/adapter';
+export const HEADER_AOTTER_VERSION = 'prebid_0.0.1';
+
+export const spec = {
+ code: BIDDER_CODE,
+ aliases: ['aotter', 'trek'],
+ supportedMediaTypes: SUPPORTED_AD_TYPES,
+
+ isBidRequestValid: (bid) => !!bid.params.placeUid && typeof bid.params.placeUid === 'string',
+
+ buildRequests: (validBidRequests, bidderRequest) => {
+ if (validBidRequests.length === 0) {
+ return [];
+ }
+
+ const clientId =
+ config.getConfig('aseal.clientId') || '';
+
+ const data = {
+ bids: validBidRequests,
+ refererInfo: bidderRequest.refererInfo,
+ };
+
+ const options = {
+ contentType: 'application/json',
+ withCredentials: true,
+ customHeaders: {
+ 'x-aotter-clientid': clientId,
+ 'x-aotter-version': HEADER_AOTTER_VERSION,
+ },
+ };
+
+ return [{
+ method: 'POST',
+ url: API_ENDPOINT,
+ data,
+ options,
+ }];
+ },
+
+ interpretResponse: (serverResponse, bidRequest) => {
+ if (!Array.isArray(serverResponse.body)) {
+ return [];
+ }
+
+ const bidResponses = serverResponse.body;
+
+ return bidResponses;
+ },
+};
+
+registerBidder(spec);
diff --git a/modules/asealBidAdapter.md b/modules/asealBidAdapter.md
new file mode 100644
index 00000000000..d13b802f736
--- /dev/null
+++ b/modules/asealBidAdapter.md
@@ -0,0 +1,52 @@
+# Overview
+
+```
+Module Name: Aseal Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: tech-service@aotter.net
+```
+
+# Description
+
+Module that connects to Aseal server for bids.
+Supported Ad Formats:
+
+- Banner
+
+# Configuration
+
+Following configuration is required:
+
+```js
+pbjs.setConfig({
+ aseal: {
+ clientId: "YOUR_CLIENT_ID"
+ }
+});
+```
+
+# Ad Unit Example
+
+```js
+var adUnits = [
+ {
+ code: "banner-div",
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 600]
+ ]
+ }
+ },
+ bids: [
+ {
+ bidder: "aseal",
+ params: {
+ placeUid: "f4a74f73-9a74-4a87-91c9-545c6316c23d"
+ }
+ }
+ ]
+ }
+];
+```
diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js
index d1e520b4b8f..f45d2e80055 100644
--- a/modules/atsAnalyticsAdapter.js
+++ b/modules/atsAnalyticsAdapter.js
@@ -20,7 +20,7 @@ export const analyticsUrl = 'https://analytics.rlcdn.com';
let handlerRequest = [];
let handlerResponse = [];
-let atsAnalyticsAdapterVersion = 2;
+let atsAnalyticsAdapterVersion = 3;
let browsersList = [
/* Googlebot */
@@ -222,7 +222,8 @@ function bidRequestedHandler(args) {
auction_start: new Date(args.auctionStart).toJSON(),
domain: window.location.hostname,
pid: atsAnalyticsAdapter.context.pid,
- adapter_version: atsAnalyticsAdapterVersion
+ adapter_version: atsAnalyticsAdapterVersion,
+ bid_won: false
};
});
return requests;
@@ -251,13 +252,14 @@ export function parseBrowser() {
}
}
-function sendDataToAnalytic () {
+function sendDataToAnalytic (events) {
// send data to ats analytic endpoint
try {
- let dataToSend = {'Data': atsAnalyticsAdapter.context.events};
+ let dataToSend = {'Data': events};
let strJSON = JSON.stringify(dataToSend);
logInfo('ATS Analytics - tried to send analytics data!');
ajax(analyticsUrl, function () {
+ logInfo('ATS Analytics - events sent successfully!');
}, strJSON, {method: 'POST', contentType: 'application/json'});
} catch (err) {
logError('ATS Analytics - request encounter an error: ', err);
@@ -265,7 +267,7 @@ function sendDataToAnalytic () {
}
// preflight request, to check did publisher have permission to send data to analytics endpoint
-function preflightRequest (envelopeSourceCookieValue) {
+function preflightRequest (envelopeSourceCookieValue, events) {
logInfo('ATS Analytics - preflight request!');
ajax(preflightUrl + atsAnalyticsAdapter.context.pid,
{
@@ -276,7 +278,8 @@ function preflightRequest (envelopeSourceCookieValue) {
atsAnalyticsAdapter.setSamplingCookie(samplingRate);
let samplingRateNumber = Number(samplingRate);
if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) {
- sendDataToAnalytic();
+ logInfo('ATS Analytics - events to send: ', events);
+ sendDataToAnalytic(events);
}
},
error: function () {
@@ -286,29 +289,6 @@ function preflightRequest (envelopeSourceCookieValue) {
}, undefined, {method: 'GET', crossOrigin: true});
}
-function callHandler(evtype, args) {
- if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) {
- handlerRequest = handlerRequest.concat(bidRequestedHandler(args));
- } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) {
- handlerResponse.push(bidResponseHandler(args));
- }
- if (evtype === CONSTANTS.EVENTS.AUCTION_END) {
- if (handlerRequest.length) {
- let events = [];
- if (handlerResponse.length) {
- events = handlerRequest.filter(request => handlerResponse.filter(function(response) {
- if (request.bid_id === response.bid_id) {
- Object.assign(request, response);
- }
- }));
- } else {
- events = handlerRequest;
- }
- atsAnalyticsAdapter.context.events = events;
- }
- }
-}
-
let atsAnalyticsAdapter = Object.assign(adapter(
{
analyticsType
@@ -316,22 +296,7 @@ let atsAnalyticsAdapter = Object.assign(adapter(
{
track({eventType, args}) {
if (typeof args !== 'undefined') {
- callHandler(eventType, args);
- }
- if (eventType === CONSTANTS.EVENTS.AUCTION_END) {
- let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats');
- try {
- let samplingRateCookie = storage.getCookie('_lr_sampling_rate');
- if (!samplingRateCookie) {
- preflightRequest(envelopeSourceCookieValue);
- } else {
- if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) {
- sendDataToAnalytic();
- }
- }
- } catch (err) {
- logError('ATS Analytics - preflight request encounter an error: ', err);
- }
+ atsAnalyticsAdapter.callHandler(eventType, args);
}
}
});
@@ -369,13 +334,69 @@ atsAnalyticsAdapter.enableAnalytics = function (config) {
}
atsAnalyticsAdapter.context = {
events: [],
- pid: config.options.pid
+ pid: config.options.pid,
+ bidWonTimeout: config.options.bidWonTimeout
};
let initOptions = config.options;
logInfo('ATS Analytics - adapter enabled! ');
atsAnalyticsAdapter.originEnableAnalytics(initOptions); // call the base class function
};
+atsAnalyticsAdapter.callHandler = function (evtype, args) {
+ if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) {
+ handlerRequest = handlerRequest.concat(bidRequestedHandler(args));
+ } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) {
+ handlerResponse.push(bidResponseHandler(args));
+ }
+ if (evtype === CONSTANTS.EVENTS.AUCTION_END) {
+ let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000;
+ let events = [];
+ setTimeout(() => {
+ let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids();
+ logInfo('ATS Analytics - winning bids: ', winningBids)
+ // prepare format data for sending to analytics endpoint
+ if (handlerRequest.length) {
+ let wonEvent = {};
+ if (handlerResponse.length) {
+ events = handlerRequest.filter(request => handlerResponse.filter(function (response) {
+ if (request.bid_id === response.bid_id) {
+ Object.assign(request, response);
+ }
+ }));
+ if (winningBids.length) {
+ events = events.filter(event => winningBids.filter(function (won) {
+ wonEvent.bid_id = won.requestId;
+ wonEvent.bid_won = true;
+ if (event.bid_id === wonEvent.bid_id) {
+ Object.assign(event, wonEvent);
+ }
+ }))
+ }
+ } else {
+ events = handlerRequest;
+ }
+ // check should we send data to analytics or not, check first cookie value _lr_sampling_rate
+ try {
+ let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats');
+ let samplingRateCookie = storage.getCookie('_lr_sampling_rate');
+ if (!samplingRateCookie) {
+ preflightRequest(envelopeSourceCookieValue, events);
+ } else {
+ if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) {
+ logInfo('ATS Analytics - events to send: ', events);
+ sendDataToAnalytic(events);
+ }
+ }
+ // empty events array to not send duplicate events
+ events = [];
+ } catch (err) {
+ logError('ATS Analytics - preflight request encounter an error: ', err);
+ }
+ }
+ }, bidWonTimeout);
+ }
+}
+
adaptermanager.registerAnalyticsAdapter({
adapter: atsAnalyticsAdapter,
code: 'atsAnalytics',
diff --git a/modules/atsAnalyticsAdapter.md b/modules/atsAnalyticsAdapter.md
index 7c634f39ae2..17819ac61b3 100644
--- a/modules/atsAnalyticsAdapter.md
+++ b/modules/atsAnalyticsAdapter.md
@@ -17,6 +17,7 @@ Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp.
provider: 'atsAnalytics',
options: {
pid: '999', // publisher ID
+ bidWonTimeout: 2000 // on auction end for how long to wait for bid_won events, by default it's 2000 miliseconds, if it's not set it will be 2000 miliseconds.
}
}
```
diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js
index a882a796851..e705156d4a2 100644
--- a/modules/beachfrontBidAdapter.js
+++ b/modules/beachfrontBidAdapter.js
@@ -1,4 +1,4 @@
-import { logWarn, deepAccess, isArray, parseSizesInput, isFn, parseUrl, getUniqueIdentifierStr } from '../src/utils.js';
+import { logWarn, deepAccess, deepSetValue, deepClone, isArray, parseSizesInput, isFn, parseUrl, getUniqueIdentifierStr } from '../src/utils.js';
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { Renderer } from '../src/Renderer.js';
@@ -6,7 +6,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js';
import find from 'core-js-pure/features/array/find.js';
import includes from 'core-js-pure/features/array/includes.js';
-const ADAPTER_VERSION = '1.18';
+const ADAPTER_VERSION = '1.19';
const ADAPTER_NAME = 'BFIO_PREBID';
const OUTSTREAM = 'outstream';
const CURRENCY = 'USD';
@@ -360,6 +360,7 @@ function createVideoRequestData(bid, bidderRequest) {
let tagid = getVideoBidParam(bid, 'tagid');
let topLocation = getTopWindowLocation(bidderRequest);
let eids = getEids(bid);
+ let ortb2 = deepClone(config.getConfig('ortb2'));
let payload = {
isPrebid: true,
appId: appId,
@@ -378,6 +379,7 @@ function createVideoRequestData(bid, bidderRequest) {
displaymanagerver: ADAPTER_VERSION
}],
site: {
+ ...deepAccess(ortb2, 'site', {}),
page: topLocation.href,
domain: topLocation.hostname
},
@@ -389,39 +391,32 @@ function createVideoRequestData(bid, bidderRequest) {
js: 1,
geo: {}
},
- regs: {
- ext: {}
- },
- source: {
- ext: {}
- },
- user: {
- ext: {}
- },
+ app: deepAccess(ortb2, 'app'),
+ user: deepAccess(ortb2, 'user'),
cur: [CURRENCY]
};
if (bidderRequest && bidderRequest.uspConsent) {
- payload.regs.ext.us_privacy = bidderRequest.uspConsent;
+ deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent);
}
if (bidderRequest && bidderRequest.gdprConsent) {
let { gdprApplies, consentString } = bidderRequest.gdprConsent;
- payload.regs.ext.gdpr = gdprApplies ? 1 : 0;
- payload.user.ext.consent = consentString;
+ deepSetValue(payload, 'regs.ext.gdpr', gdprApplies ? 1 : 0);
+ deepSetValue(payload, 'user.ext.consent', consentString);
}
if (bid.schain) {
- payload.source.ext.schain = bid.schain;
+ deepSetValue(payload, 'source.ext.schain', bid.schain);
}
if (eids.length > 0) {
- payload.user.ext.eids = eids;
+ deepSetValue(payload, 'user.ext.eids', eids);
}
let connection = navigator.connection || navigator.webkitConnection;
if (connection && connection.effectiveType) {
- payload.device.connectiontype = connection.effectiveType;
+ deepSetValue(payload, 'device.connectiontype', connection.effectiveType);
}
return payload;
@@ -439,8 +434,10 @@ function createBannerRequestData(bids, bidderRequest) {
sizes: getBannerSizes(bid)
};
});
+ let ortb2 = deepClone(config.getConfig('ortb2'));
let payload = {
slots: slots,
+ ortb2: ortb2,
page: topLocation.href,
domain: topLocation.hostname,
search: topLocation.search,
diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js
index a6bc8a5687d..2e74170fcaf 100644
--- a/modules/beopBidAdapter.js
+++ b/modules/beopBidAdapter.js
@@ -36,7 +36,7 @@ export const spec = {
*/
buildRequests: function(validBidRequests, bidderRequest) {
const slots = validBidRequests.map(beOpRequestSlotsMaker);
- let pageUrl = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href');
+ let pageUrl = deepAccess(window, 'location.href') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl');
let fpd = config.getLegacyFpd(config.getConfig('ortb2'));
let gdpr = bidderRequest.gdprConsent;
let firstSlot = slots[0];
@@ -99,16 +99,18 @@ export const spec = {
}
function buildTrackingParams(data, info, value) {
+ const accountId = data.params.accountId;
return {
- pid: data.params.accountId,
+ pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId,
nid: data.params.networkId,
nptnid: data.params.networkPartnerId,
- bid: data.bidId,
+ bid: data.bidId || data.requestId,
sl_n: data.adUnitCode,
aid: data.auctionId,
se_ca: 'bid',
se_ac: info,
- se_va: value
+ se_va: value,
+ url: window.location.href
};
}
diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js
index 7ac1e4edf15..b2f63488e12 100644
--- a/modules/betweenBidAdapter.js
+++ b/modules/betweenBidAdapter.js
@@ -3,12 +3,14 @@ import { getAdUnitSizes, parseSizesInput } from '../src/utils.js';
import { getRefererInfo } from '../src/refererDetection.js';
const BIDDER_CODE = 'between';
-const ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid';
+let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid';
+const CODE_TYPES = ['inpage', 'preroll', 'midroll', 'postroll'];
+const includes = require('core-js-pure/features/array/includes.js');
export const spec = {
code: BIDDER_CODE,
aliases: ['btw'],
- supportedMediaTypes: ['banner'],
+ supportedMediaTypes: ['banner', 'video'],
/**
* Determines whether or not the given bid request is valid.
*
@@ -30,6 +32,8 @@ export const spec = {
const refInfo = getRefererInfo();
validBidRequests.forEach((i) => {
+ const video = i.mediaTypes && i.mediaTypes.video;
+
let params = {
eids: getUsersIds(i),
sizes: parseSizesInput(getAdUnitSizes(i)),
@@ -38,12 +42,21 @@ export const spec = {
tz: getTz(),
fl: getFl(),
rr: getRr(),
- s: i.params.s,
+ s: i.params && i.params.s,
bidid: i.bidId,
transactionid: i.transactionId,
auctionid: i.auctionId
};
+ if (video) {
+ params.mediaType = 2;
+ params.maxd = video.maxd;
+ params.mind = video.mind;
+ params.pos = 'atf';
+ ENDPOINT += '&jst=pvc';
+ params.codeType = includes(CODE_TYPES, video.codeType) ? video.codeType : 'inpage';
+ }
+
if (i.params.itu !== undefined) {
params.itu = i.params.itu;
}
@@ -94,12 +107,15 @@ export const spec = {
*/
interpretResponse: function(serverResponse, bidRequest) {
const bidResponses = [];
+
for (var i = 0; i < serverResponse.body.length; i++) {
let bidResponse = {
requestId: serverResponse.body[i].bidid,
cpm: serverResponse.body[i].cpm || 0,
width: serverResponse.body[i].w,
height: serverResponse.body[i].h,
+ vastXml: serverResponse.body[i].vastXml,
+ mediaType: serverResponse.body[i].mediaType,
ttl: serverResponse.body[i].ttl,
creativeId: serverResponse.body[i].creativeid,
currency: serverResponse.body[i].currency || 'RUB',
@@ -109,6 +125,7 @@ export const spec = {
advertiserDomains: serverResponse.body[i].adomain ? serverResponse.body[i].adomain : []
}
};
+
bidResponses.push(bidResponse);
}
return bidResponses;
diff --git a/modules/bidViewability.js b/modules/bidViewability.js
index c3b72cda8d4..545d57940da 100644
--- a/modules/bidViewability.js
+++ b/modules/bidViewability.js
@@ -4,7 +4,7 @@
import { config } from '../src/config.js';
import * as events from '../src/events.js';
-import { EVENTS } from '../src/constants.json';
+import CONSTANTS from '../src/constants.json';
import { logWarn, isFn, triggerPixel } from '../src/utils.js';
import { getGlobal } from '../src/prebidGlobal.js';
import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js';
@@ -70,12 +70,12 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => {
// trigger respective bidder's onBidViewable handler
adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid);
// emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels
- events.emit(EVENTS.BID_VIEWABLE, respectiveBid);
+ events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, respectiveBid);
}
};
export let init = () => {
- events.on(EVENTS.AUCTION_INIT, () => {
+ events.on(CONSTANTS.EVENTS.AUCTION_INIT, () => {
// read the config for the module
const globalModuleConfig = config.getConfig(MODULE_NAME) || {};
// do nothing if module-config.enabled is not set to true
diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js
index d936fb4aeec..ff7ec70e32c 100644
--- a/modules/bidViewabilityIO.js
+++ b/modules/bidViewabilityIO.js
@@ -1,7 +1,7 @@
import { logMessage } from '../src/utils.js';
import { config } from '../src/config.js';
import * as events from '../src/events.js';
-import { EVENTS } from '../src/constants.json';
+import CONSTANTS from '../src/constants.json';
const MODULE_NAME = 'bidViewabilityIO';
const CONFIG_ENABLED = 'enabled';
@@ -42,7 +42,7 @@ export let getViewableOptions = (bid) => {
export let markViewed = (bid, entry, observer) => {
return () => {
observer.unobserve(entry.target);
- events.emit(EVENTS.BID_VIEWABLE, bid);
+ events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, bid);
_logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`);
}
}
@@ -77,7 +77,7 @@ export let init = () => {
if (conf[MODULE_NAME][CONFIG_ENABLED] && CLIENT_SUPPORTS_IO) {
// if the module is enabled and the browser supports Intersection Observer,
// then listen to AD_RENDER_SUCCEEDED to setup IO's for supported mediaTypes
- events.on(EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => {
+ events.on(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => {
if (isSupportedMediaType(bid)) {
let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid));
let element = document.getElementById(bid.adUnitCode);
diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js
index 2f4cb9beac6..45b6c46c2df 100644
--- a/modules/bliinkBidAdapter.js
+++ b/modules/bliinkBidAdapter.js
@@ -1,7 +1,6 @@
// eslint-disable-next-line prebid/validate-imports
// eslint-disable-next-line prebid/validate-imports
-import {registerBidder} from 'src/adapters/bidderFactory.js'
-
+import {registerBidder} from '../src/adapters/bidderFactory.js'
export const BIDDER_CODE = 'bliink'
export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery'
export const BLIINK_ENDPOINT_ENGINE_VAST = 'https://engine.bliink.io/vast'
@@ -10,10 +9,9 @@ export const META_KEYWORDS = 'keywords'
export const META_DESCRIPTION = 'description'
const VIDEO = 'video'
-const NATIVE = 'native'
const BANNER = 'banner'
-const supportedMediaTypes = [BANNER, VIDEO, NATIVE]
+const supportedMediaTypes = [BANNER, VIDEO]
const aliasBidderCode = ['bk']
export function getMetaList(name) {
@@ -90,7 +88,11 @@ export const parseXML = (content) => {
if (typeof content !== 'string' || content.length === 0) return null
const parser = new DOMParser()
- const xml = parser.parseFromString(content, 'text/xml')
+ let xml;
+
+ try {
+ xml = parser.parseFromString(content, 'text/xml')
+ } catch (e) {}
if (xml &&
xml.getElementsByTagName('VAST')[0] &&
@@ -104,19 +106,19 @@ export const parseXML = (content) => {
/**
* @param bidRequest
* @param bliinkCreative
- * @return {{cpm, netRevenue: boolean, ad: string, requestId, width: number, currency: string, mediaType: string, vastXml, ttl: number, height: number}|null}
+ * @return {{cpm, netRevenue: boolean, requestId, width: (*|number), currency, ttl: number, creativeId, height: (*|number)} & {mediaType: string, vastXml}}
*/
export const buildBid = (bidRequest, bliinkCreative) => {
if (!bidRequest && !bliinkCreative) return null
const body = {
requestId: bidRequest.bidId,
+ currency: bliinkCreative.currency,
cpm: bliinkCreative.price,
creativeId: bliinkCreative.creativeId,
- currency: 'EUR',
+ width: (bidRequest.sizes && bidRequest.sizes[0][0]) || 1,
+ height: (bidRequest.sizes && bidRequest.sizes[0][1]) || 1,
netRevenue: false,
- width: 1,
- height: 1,
ttl: 3600,
}
@@ -131,14 +133,20 @@ export const buildBid = (bidRequest, bliinkCreative) => {
delete bidRequest['bids']
- return Object.assign(body, {
- currency: bliinkCreative.currency,
- width: 1,
- height: 1,
- mediaType: VIDEO,
- ad: '',
- vastXml: bliinkCreative.content,
- })
+ switch (bliinkCreative.media_type) {
+ case VIDEO:
+ return Object.assign(body, {
+ mediaType: VIDEO,
+ vastXml: bliinkCreative.content,
+ })
+ case BANNER:
+ return Object.assign(body, {
+ mediaType: BANNER,
+ ad: (bliinkCreative && bliinkCreative.content && bliinkCreative.content.creative && bliinkCreative.content.creative.adm) || '',
+ })
+ default:
+ break;
+ }
}
/**
@@ -165,6 +173,8 @@ export const buildRequests = (_, bidderRequest) => {
pageUrl: bidderRequest.refererInfo.referer,
pageDescription: getMetaValue(META_DESCRIPTION),
keywords: getKeywords().join(','),
+ gdpr: false,
+ gdpr_consent: '',
pageTitle: document.title,
}
@@ -209,7 +219,7 @@ export const buildRequests = (_, bidderRequest) => {
* @return
*/
const interpretResponse = (serverResponse, request) => {
- if ((serverResponse && serverResponse.mode === 'no-ad') && (!request.params)) {
+ if ((serverResponse && serverResponse.mode === 'no-ad')) {
return []
}
@@ -218,23 +228,40 @@ const interpretResponse = (serverResponse, request) => {
const xml = parseXML(body)
- if (xml) {
- const price = xml.getElementsByTagName('Price') && xml.getElementsByTagName('Price')[0]
- const currency = xml.getElementsByTagName('Currency') && xml.getElementsByTagName('Currency')[0]
- const creativeId = xml.getElementsByTagName('CreativeId') && xml.getElementsByTagName('CreativeId')[0]
-
- const creative = {
- content: body,
- price: (price && price.textContent) || 0,
- currency: (currency && currency.textContent) || 'EUR',
- creativeId: creativeId || 0,
- media_type: 'video',
- }
-
- return buildBid(serverBody.bids[0], creative);
+ let creative;
+
+ switch (serverBody.bids[0].params.placement) {
+ case xml && VIDEO:
+ const price = xml.getElementsByTagName('Price') && xml.getElementsByTagName('Price')[0]
+ const currency = xml.getElementsByTagName('Currency') && xml.getElementsByTagName('Currency')[0]
+ const creativeId = xml.getElementsByTagName('CreativeId') && xml.getElementsByTagName('CreativeId')[0]
+
+ creative = {
+ content: body,
+ price: (price && price.textContent) || 0,
+ currency: (currency && currency.textContent) || 'EUR',
+ creativeId: creativeId || 0,
+ media_type: 'video',
+ }
+
+ return buildBid(serverBody.bids[0], creative)
+ case BANNER:
+ if (body) {
+ creative = {
+ content: body,
+ price: body.price,
+ currency: body.currency,
+ creativeId: 0,
+ media_type: 'banner',
+ }
+
+ return buildBid(serverBody.bids[0], creative)
+ }
+
+ break
+ default:
+ break
}
-
- return []
}
/**
diff --git a/modules/bliinkBidAdapter.md b/modules/bliinkBidAdapter.md
index ae0d4275396..af7aee3a1ae 100644
--- a/modules/bliinkBidAdapter.md
+++ b/modules/bliinkBidAdapter.md
@@ -31,7 +31,7 @@ const adUnits = [
bidder: 'bliink',
params: {
placement: 'banner',
- tagId: '14f30eca-85d2-11e8-9eed-0242ac120007'
+ tagId: '41'
}
}
]
@@ -50,11 +50,34 @@ const adUnits = [
mediaTypes: {
video: {
context: 'instream',
- playerSize: [640, 480],
- mimes: ['video/mp4'],
- protocols: [1, 2, 3, 4, 5, 6, 7, 8],
- playbackmethod: [2],
- skip: 1
+ playerSize: [[640,480]],
+ }
+ },
+ bids: [
+ {
+ bidder: 'bliink',
+ params: {
+ tagId: '41',
+ placement: 'video',
+ }
+ }
+ ]
+ }
+]
+```
+
+## Sample outstream Video Ad Unit
+
+```js
+const adUnits = [
+ {
+ code: '/19968336/prebid_cache_video_adunit',
+ sizes: [[640,480]],
+ mediaType: 'video',
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [[640,480]],
}
},
bids: [
diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js
new file mode 100644
index 00000000000..60d3c98f15e
--- /dev/null
+++ b/modules/brandmetricsRtdProvider.js
@@ -0,0 +1,168 @@
+/**
+ * This module adds brandmetrics provider to the real time data module
+ * The {@link module:modules/realTimeData} module is required
+ * The module will load load the brandmetrics script and set survey- targeting to ad units of specific bidders.
+ * @module modules/brandmetricsRtdProvider
+ * @requires module:modules/realTimeData
+ */
+import { config } from '../src/config.js'
+import { submodule } from '../src/hook.js'
+import { deepSetValue, mergeDeep, logError, deepAccess } from '../src/utils.js'
+import {loadExternalScript} from '../src/adloader.js'
+const MODULE_NAME = 'brandmetrics'
+const MODULE_CODE = MODULE_NAME
+const RECEIVED_EVENTS = []
+const GVL_ID = 422
+const TCF_PURPOSES = [1, 7]
+
+function init (config, userConsent) {
+ const hasConsent = checkConsent(userConsent)
+
+ if (hasConsent) {
+ const moduleConfig = getMergedConfig(config)
+ initializeBrandmetrics(moduleConfig.params.scriptId)
+ }
+ return hasConsent
+}
+
+/**
+ * Checks TCF and USP consents
+ * @param {Object} userConsent
+ * @returns {boolean}
+ */
+function checkConsent (userConsent) {
+ let consent = false
+
+ if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) {
+ const gdpr = userConsent.gdpr
+
+ if (gdpr.vendorData) {
+ const vendor = gdpr.vendorData.vendor
+ const purpose = gdpr.vendorData.purpose
+
+ let vendorConsent = false
+ if (vendor.consents) {
+ vendorConsent = vendor.consents[GVL_ID]
+ }
+
+ if (vendor.legitimateInterests) {
+ vendorConsent = vendorConsent || vendor.legitimateInterests[GVL_ID]
+ }
+
+ const purposes = TCF_PURPOSES.map(id => {
+ return (purpose.consents && purpose.consents[id]) || (purpose.legitimateInterests && purpose.legitimateInterests[id])
+ })
+ const purposesValid = purposes.filter(p => p === true).length === TCF_PURPOSES.length
+ consent = vendorConsent && purposesValid
+ }
+ } else if (userConsent.usp) {
+ const usp = userConsent.usp
+ consent = usp[1] !== 'N' && usp[2] !== 'Y'
+ }
+
+ return consent
+}
+
+/**
+* Add event- listeners to hook in to brandmetrics events
+* @param {Object} reqBidsConfigObj
+* @param {function} callback
+*/
+function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) {
+ const callBidTargeting = (event) => {
+ if (event.available && event.conf) {
+ const targetingConf = event.conf.displayOption || {}
+ if (targetingConf.type === 'pbjs') {
+ setBidderTargeting(reqBidsConfigObj, moduleConfig, targetingConf.targetKey || 'brandmetrics_survey', event.survey.measurementId)
+ }
+ }
+ callback()
+ }
+
+ if (RECEIVED_EVENTS.length > 0) {
+ callBidTargeting(RECEIVED_EVENTS[RECEIVED_EVENTS.length - 1])
+ } else {
+ window._brandmetrics = window._brandmetrics || []
+ window._brandmetrics.push({
+ cmd: '_addeventlistener',
+ val: {
+ event: 'surveyloaded',
+ reEmitLast: true,
+ handler: (ev) => {
+ RECEIVED_EVENTS.push(ev)
+ if (RECEIVED_EVENTS.length === 1) {
+ // Call bid targeting only for the first received event, if called subsequently, last event from the RECEIVED_EVENTS array is used
+ callBidTargeting(ev)
+ }
+ },
+ }
+ })
+ }
+}
+
+/**
+ * Sets bid targeting of specific bidders
+ * @param {Object} reqBidsConfigObj
+ * @param {string} key Targeting key
+ * @param {string} val Targeting value
+ */
+function setBidderTargeting (reqBidsConfigObj, moduleConfig, key, val) {
+ const bidders = deepAccess(moduleConfig, 'params.bidders')
+ if (bidders && bidders.length > 0) {
+ const ortb2 = {}
+ deepSetValue(ortb2, 'ortb2.user.ext.data.' + key, val)
+ config.setBidderConfig({
+ bidders: bidders,
+ config: ortb2
+ })
+ }
+}
+
+/**
+ * Add the brandmetrics script to the page.
+ * @param {string} scriptId - The script- id provided by brandmetrics or brandmetrics partner
+ */
+function initializeBrandmetrics(scriptId) {
+ if (scriptId) {
+ const path = 'https://cdn.brandmetrics.com/survey/script/'
+ const file = scriptId + '.js'
+ const url = path + file
+
+ loadExternalScript(url, MODULE_CODE)
+ }
+}
+
+/**
+ * Merges a provided config with default values
+ * @param {Object} customConfig
+ * @returns
+ */
+function getMergedConfig(customConfig) {
+ return mergeDeep({
+ waitForIt: false,
+ params: {
+ bidders: [],
+ scriptId: undefined,
+ }
+ }, customConfig)
+}
+
+/** @type {RtdSubmodule} */
+export const brandmetricsSubmodule = {
+ name: MODULE_NAME,
+ getBidRequestData: function (reqBidsConfigObj, callback, customConfig) {
+ try {
+ const moduleConfig = getMergedConfig(customConfig)
+ if (moduleConfig.waitForIt) {
+ processBrandmetricsEvents(reqBidsConfigObj, moduleConfig, callback)
+ } else {
+ callback()
+ }
+ } catch (e) {
+ logError(e)
+ }
+ },
+ init: init
+}
+
+submodule('realTimeData', brandmetricsSubmodule)
diff --git a/modules/brandmetricsRtdProvider.md b/modules/brandmetricsRtdProvider.md
new file mode 100644
index 00000000000..89ee6bb75cf
--- /dev/null
+++ b/modules/brandmetricsRtdProvider.md
@@ -0,0 +1,40 @@
+# Brandmetrics Real-time Data Submodule
+This module is intended to be used by brandmetrics (https://brandmetrics.com) partners and sets targeting keywords to bids if the browser is eligeble to see a brandmetrics survey.
+The module hooks in to brandmetrics events and requires a brandmetrics script to be running. The module can optionally load and initialize brandmetrics by providing the 'scriptId'- parameter.
+
+## Usage
+Compile the Brandmetrics RTD module into your Prebid build:
+```
+gulp build --modules=rtdModule,brandmetricsRtdProvider
+```
+
+> Note that the global RTD module, `rtdModule`, is a prerequisite of the Brandmetrics RTD module.
+
+Enable the Brandmetrics RTD in your Prebid configuration, using the below format:
+
+```javascript
+pbjs.setConfig({
+ ...,
+ realTimeData: {
+ auctionDelay: 500, // auction delay
+ dataProviders: [{
+ name: 'brandmetrics',
+ waitForIt: true // should be true if there's an `auctionDelay`,
+ params: {
+ scriptId: '00000000-0000-0000-0000-000000000000',
+ bidders: ['ozone']
+ }
+ }]
+ },
+ ...
+})
+```
+
+## Parameters
+| Name | Type | Description | Default |
+| ----------------- | -------------------- | ------------------ | ------------------ |
+| name | String | This should always be `brandmetrics` | - |
+| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (recommended) | `false` |
+| params | Object | | - |
+| params.bidders | String[] | An array of bidders which should receive targeting keys. | `[]` |
+| params.scriptId | String | A script- id GUID if the brandmetrics- script should be initialized. | `undefined` |
diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js
index d527223964e..a1943afda8d 100644
--- a/modules/browsiRtdProvider.js
+++ b/modules/browsiRtdProvider.js
@@ -15,21 +15,27 @@
* @property {?string} keyName
*/
-import { deepClone, logError, isGptPubadsDefined } from '../src/utils.js';
+import { deepClone, logError, isGptPubadsDefined, isNumber, isFn, deepSetValue } from '../src/utils.js';
import {submodule} from '../src/hook.js';
import {ajaxBuilder} from '../src/ajax.js';
import {loadExternalScript} from '../src/adloader.js';
import {getStorageManager} from '../src/storageManager.js';
import find from 'core-js-pure/features/array/find.js';
+import {getGlobal} from '../src/prebidGlobal.js';
+import includes from 'core-js-pure/features/array/includes.js';
const storage = getStorageManager();
/** @type {ModuleParams} */
let _moduleParams = {};
/** @type {null|Object} */
-let _predictionsData = null;
+let _browsiData = null;
/** @type {string} */
const DEF_KEYNAME = 'browsiViewability';
+/** @type {null | function} */
+let _dataReadyCallback = null;
+/** @type {null|Object} */
+let _ic = {};
/**
* add browsi script to page
@@ -78,29 +84,49 @@ export function collectData() {
getPredictionsFromServer(`//${_moduleParams.url}/prebid?${toUrlParams(predictorData)}`);
}
+/**
+ * wait for data from server
+ * call callback when data is ready
+ * @param {function} callback
+ */
+function waitForData(callback) {
+ if (_browsiData) {
+ _dataReadyCallback = null;
+ callback(_browsiData);
+ } else {
+ _dataReadyCallback = callback;
+ }
+}
+
export function setData(data) {
- _predictionsData = data;
+ _browsiData = data;
+ if (isFn(_dataReadyCallback)) {
+ _dataReadyCallback(_browsiData);
+ _dataReadyCallback = null;
+ }
}
-function sendDataToModule(adUnitsCodes) {
+function getRTD(auc) {
try {
- const _predictions = (_predictionsData && _predictionsData.p) || {};
- return adUnitsCodes.reduce((rp, adUnitCode) => {
- if (!adUnitCode) {
+ const _bp = (_browsiData && _browsiData.p) || {};
+ return auc.reduce((rp, uc) => {
+ _ic[uc] = _ic[uc] || 0;
+ const _c = _ic[uc];
+ if (!uc) {
return rp
}
- const adSlot = getSlotByCode(adUnitCode);
- const identifier = adSlot ? getMacroId(_predictionsData['pmd'], adSlot) : adUnitCode;
- const predictionData = _predictions[identifier];
- rp[adUnitCode] = getKVObject(-1, _predictionsData['kn']);
- if (!predictionData) {
+ const adSlot = getSlotByCode(uc);
+ const identifier = adSlot ? getMacroId(_browsiData['pmd'], adSlot) : uc;
+ const _pd = _bp[identifier];
+ rp[uc] = getKVObject(-1);
+ if (!_pd) {
return rp
}
- if (predictionData.p) {
- if (!isIdMatchingAdUnit(adSlot, predictionData.w)) {
+ if (_pd.ps) {
+ if (!isIdMatchingAdUnit(adSlot, _pd.w)) {
return rp;
}
- rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn);
+ rp[uc] = getKVObject(getCurrentData(_pd.ps, _c));
}
return rp;
}, {});
@@ -109,6 +135,31 @@ function sendDataToModule(adUnitsCodes) {
}
}
+/**
+ * get prediction
+ * return -1 if prediction not found
+ * @param {object} predictionObject
+ * @param {number} _c
+ * @return {number}
+ */
+export function getCurrentData(predictionObject, _c) {
+ if (!predictionObject || !isNumber(_c)) {
+ return -1;
+ }
+ if (isNumber(predictionObject[_c])) {
+ return predictionObject[_c];
+ }
+ if (Object.keys(predictionObject).length > 1) {
+ while (_c > 0) {
+ _c--;
+ if (isNumber(predictionObject[_c])) {
+ return predictionObject[_c];
+ }
+ }
+ }
+ return -1;
+}
+
/**
* get all slots on page
* @return {Object[]} slot GoogleTag slots
@@ -122,12 +173,16 @@ function getAllSlots() {
* @param {string?} keyName
* @return {Object} key:value
*/
-function getKVObject(p, keyName) {
+function getKVObject(p) {
const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2);
let prObject = {};
- prObject[((_moduleParams['keyName'] || keyName || DEF_KEYNAME).toString())] = prValue.toString();
+ prObject[getKey()] = prValue.toString();
return prObject;
}
+
+function getKey() {
+ return ((_moduleParams['keyName'] || (_browsiData && _browsiData['kn']) || DEF_KEYNAME).toString())
+}
/**
* check if placement id matches one of given ad units
* @param {Object} slot google slot
@@ -238,6 +293,28 @@ function toUrlParams(data) {
.join('&');
}
+function setBidRequestsData(bidObj, callback) {
+ let adUnitCodes = bidObj.adUnitCodes;
+ let adUnits = bidObj.adUnits || getGlobal().adUnits || [];
+ if (adUnitCodes) {
+ adUnits = adUnits.filter(au => includes(adUnitCodes, au.code));
+ } else {
+ adUnitCodes = adUnits.map(au => au.code);
+ }
+ waitForData(() => {
+ const data = getRTD(adUnitCodes);
+ if (data) {
+ adUnits.forEach(adUnit => {
+ const adUnitCode = adUnit.code;
+ if (data[adUnitCode]) {
+ deepSetValue(adUnit, 'ortb2Imp.ext.data.browsi', {[getKey()]: data[adUnitCode][getKey()]});
+ }
+ });
+ }
+ callback();
+ })
+}
+
/** @type {RtdSubmodule} */
export const browsiSubmodule = {
/**
@@ -250,10 +327,21 @@ export const browsiSubmodule = {
* @function
* @param {string[]} adUnitsCodes
*/
- getTargetingData: sendDataToModule,
+ getTargetingData: getTargetingData,
init: init,
+ getBidRequestData: setBidRequestsData
};
+function getTargetingData(uc) {
+ const targetingData = getRTD(uc);
+ uc.forEach(auc => {
+ if (isNumber(_ic[auc])) {
+ _ic[auc] = _ic[auc] + 1;
+ }
+ });
+ return targetingData;
+}
+
function init(moduleConfig) {
_moduleParams = moduleConfig.params;
if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) {
diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js
new file mode 100644
index 00000000000..b9fdcef768e
--- /dev/null
+++ b/modules/cleanioRtdProvider.js
@@ -0,0 +1,192 @@
+/**
+ * This module adds clean.io provider to the real time data module
+ * The {@link module:modules/realTimeData} module is required
+ * The module will wrap bid responses markup in clean.io agent script for protection
+ * @module modules/cleanioRtdProvider
+ * @requires module:modules/realTimeData
+ */
+
+import { submodule } from '../src/hook.js';
+import { logError, generateUUID, insertElement } from '../src/utils.js';
+
+// ============================ MODULE STATE ===============================
+
+/**
+ * @type {function(): void}
+ * Page-wide initialization step / strategy
+ */
+let onModuleInit = () => {};
+
+/**
+ * @type {function(Object): void}
+ * Bid response mutation step / strategy.
+ */
+let onBidResponse = () => {};
+
+/**
+ * @type {number}
+ * 0 for unknown, 1 for preloaded, -1 for error.
+ */
+let preloadStatus = 0;
+
+// ============================ MODULE LOGIC ===============================
+
+/**
+ * Page initialization step which just preloads the script, to be available whenever we start processing the bids.
+ * @param {string} scriptURL The script URL to preload
+ */
+function pageInitStepPreloadScript(scriptURL) {
+ const linkElement = document.createElement('link');
+ linkElement.rel = 'preload';
+ linkElement.as = 'script';
+ linkElement.href = scriptURL;
+ linkElement.onload = () => { preloadStatus = 1; };
+ linkElement.onerror = () => { preloadStatus = -1; };
+ insertElement(linkElement);
+}
+
+/**
+ * Page initialization step which adds the protector script to the whole page. With that, there is no need wrapping bids, and the coverage is better.
+ * @param {string} scriptURL The script URL to add to the page for protection
+ */
+function pageInitStepProtectPage(scriptURL) {
+ const scriptElement = document.createElement('script');
+ scriptElement.type = 'text/javascript';
+ scriptElement.src = scriptURL;
+ insertElement(scriptElement);
+}
+
+/**
+ * Bid processing step which alters the ad HTML to contain bid-specific information, which can be used to identify the creative later.
+ * @param {Object} bidResponse Bid response data
+ */
+function bidWrapStepAugmentHtml(bidResponse) {
+ bidResponse.ad = `\n${bidResponse.ad}`;
+}
+
+/**
+ * Bid processing step which applies creative protection by wrapping the ad HTML.
+ * @param {string} scriptURL
+ * @param {number} requiredPreload
+ * @param {Object} bidResponse
+ */
+function bidWrapStepProtectByWrapping(scriptURL, requiredPreload, bidResponse) {
+ // Still prepend bid info, it's always helpful to have creative data in its payload
+ bidWrapStepAugmentHtml(bidResponse);
+
+ // If preloading failed, or if configuration requires us to finish preloading -
+ // we should not process this bid any further
+ if (preloadStatus < requiredPreload) {
+ return;
+ }
+
+ const sid = generateUUID();
+ bidResponse.ad = `
+
+
+ `;
+}
+
+/**
+ * Custom error class to differentiate validation errors
+ */
+class ConfigError extends Error { }
+
+/**
+ * The function to be called upon module init. Depending on the passed config, initializes properly init/bid steps or throws ConfigError.
+ * @param {Object} config
+ */
+function readConfig(config) {
+ if (!config.params) {
+ throw new ConfigError('Missing config parameters for clean.io RTD module provider.');
+ }
+
+ if (typeof config.params.cdnUrl !== 'string' || !/^https?:\/\//.test(config.params.cdnUrl)) {
+ throw new ConfigError('Parameter "cdnUrl" is a required string parameter, which should start with "http(s)://".');
+ }
+
+ if (typeof config.params.protectionMode !== 'string') {
+ throw new ConfigError('Parameter "protectionMode" is a required string parameter.');
+ }
+
+ const scriptURL = config.params.cdnUrl;
+
+ switch (config.params.protectionMode) {
+ case 'full':
+ onModuleInit = () => pageInitStepProtectPage(scriptURL);
+ onBidResponse = (bidResponse) => bidWrapStepAugmentHtml(bidResponse);
+ break;
+
+ case 'bids':
+ onModuleInit = () => pageInitStepPreloadScript(scriptURL);
+ onBidResponse = (bidResponse) => bidWrapStepProtectByWrapping(scriptURL, 0, bidResponse);
+ break;
+
+ case 'bids-nowait':
+ onModuleInit = () => pageInitStepPreloadScript(scriptURL);
+ onBidResponse = (bidResponse) => bidWrapStepProtectByWrapping(scriptURL, 1, bidResponse);
+ break;
+
+ default:
+ throw new ConfigError('Parameter "protectionMode" must be one of "full" | "bids" | "bids-nowait".');
+ }
+}
+
+// ============================ MODULE REGISTRATION ===============================
+
+/**
+ * The function which performs submodule registration.
+ */
+function beforeInit() {
+ submodule('realTimeData', /** @type {RtdSubmodule} */ ({
+ name: 'clean.io',
+
+ init: (config, userConsent) => {
+ try {
+ readConfig(config);
+ onModuleInit();
+ return true;
+ } catch (err) {
+ if (err instanceof ConfigError) {
+ logError(err.message);
+ }
+ return false;
+ }
+ },
+
+ onBidResponseEvent: (bidResponse, config, userConsent) => {
+ onBidResponse(bidResponse);
+ }
+ }));
+}
+
+/**
+ * Exporting local (and otherwise encapsulated to this module) functions
+ * for testing purposes
+ */
+export const __TEST__ = {
+ pageInitStepPreloadScript,
+ pageInitStepProtectPage,
+ bidWrapStepAugmentHtml,
+ bidWrapStepProtectByWrapping,
+ ConfigError,
+ readConfig,
+ beforeInit,
+}
+
+beforeInit();
diff --git a/modules/cleanioRtdProvider.md b/modules/cleanioRtdProvider.md
new file mode 100644
index 00000000000..7870a2719b6
--- /dev/null
+++ b/modules/cleanioRtdProvider.md
@@ -0,0 +1,59 @@
+# Overview
+
+```
+Module Name: clean.io Rtd provider
+Module Type: Rtd Provider
+Maintainer: nick@clean.io
+```
+
+The clean.io Realtime module provides effective anti-malvertising solution for publishers, including, but not limited to,
+blocking unwanted 0- and 1-click redirects, deceptive ads or those with malicious landing pages, and various types of affiliate fraud.
+
+Using this module requires prior agreement with [clean.io](https://clean.io) to obtain the necessary distribution key.
+
+
+# Integration
+
+clean.io Realtime module can be built just like any other prebid module:
+
+```
+gulp build --modules=cleanioRtdProvider,...
+```
+
+
+# Configuration
+
+When built into prebid.js, this module can be configured through the following `pbjs.setConfig` call:
+
+```javascript
+pbjs.setConfig({
+ realTimeData: {
+ dataProviders: [{
+ name: 'clean.io',
+ params: {
+ cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', ///< Contact clean.io to get your own CDN URL
+ protectionMode: 'full', ///< Supported modes are 'full', 'bids' and 'bids-nowait', see below.
+ }
+ }]
+ }
+});
+```
+
+
+## Configuration parameters
+
+{: .table .table-bordered .table-striped }
+| Name | Type | Scope | Description |
+| :------------ | :------------ | :------------ |:------------ |
+| ``cdnUrl`` | ``string`` | Required | CDN URL of the script, which is to be used for protection. |
+| ``protectionMode`` | ``'full' \| 'bids' \| 'bids-nowait'`` | Required | Integration mode. Please refer to the "Integration modes" section for details. |
+
+
+## Integration modes
+
+{: .table .table-bordered .table-striped }
+| Integration Mode | Parameter Value | Description |
+| :------------ | :------------ | :------------ |
+| Full page protection | ``'full'`` | Preferred mode. The module will add the protector agent script directly to the page, and it will protect all placements. This mode will make the most out of various behavioral detection mechanisms, and will also prevent typical malicious behaviors. Please note that in this mode, depending on Prebid library naming, Chrome may mistakenly tag non-ad-related content as ads: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/ad_tagging.md. |
+| Bids-only protection | ``'bids'`` | The module will protect specific bid responses, more specifically, the HTML representing ad payload, by wrapping it into the agent script. Please note that in this mode, ads delivered directly, outside of Prebid integration, will not be protected, since the module can only access the ads coming through Prebid. |
+| Bids-only protection with no delay on bid rendering | ``'bids-nowait'`` | Same as above, but in this mode, the script will also *not* wrap those bid responses, which arrived prior to successful preloading of agent script. |
diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js
new file mode 100644
index 00000000000..b9da86ac24e
--- /dev/null
+++ b/modules/codefuelBidAdapter.js
@@ -0,0 +1,181 @@
+import { deepAccess, isArray } from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
+const BIDDER_CODE = 'codefuel';
+const CURRENCY = 'USD';
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [ BANNER ],
+ aliases: ['ex'], // short code
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function(bid) {
+ if (bid.nativeParams) {
+ return false;
+ }
+ return !!(bid.params.placementId || (bid.params.member && bid.params.invCode));
+ },
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function(validBidRequests, bidderRequest) {
+ const page = bidderRequest.refererInfo.referer;
+ const domain = getDomainFromURL(page)
+ const ua = navigator.userAgent;
+ const devicetype = getDeviceType()
+ const publisher = setOnAny(validBidRequests, 'params.publisher');
+ const cur = CURRENCY;
+ const endpointUrl = 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid'
+ const timeout = bidderRequest.timeout;
+
+ validBidRequests.forEach(bid => bid.netRevenue = 'net');
+
+ const imps = validBidRequests.map((bid, idx) => {
+ const imp = {
+ id: idx + 1 + ''
+ }
+
+ if (bid.params.tagid) {
+ imp.tagid = bid.params.tagid
+ }
+
+ if (bid.sizes) {
+ imp.banner = {
+ format: transformSizes(bid.sizes)
+ }
+ }
+
+ return imp;
+ });
+
+ const request = {
+ id: bidderRequest.auctionId,
+ site: { page, domain, publisher },
+ device: { ua, devicetype },
+ source: { fd: 1 },
+ cur: [cur],
+ tmax: timeout,
+ imp: imps,
+ };
+
+ return {
+ method: 'POST',
+ url: endpointUrl,
+ data: request,
+ bids: validBidRequests,
+ options: {
+ withCredentials: false
+ }
+ };
+ },
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: (serverResponse, { bids }) => {
+ if (!serverResponse.body) {
+ return [];
+ }
+ const { seatbid, cur } = serverResponse.body;
+
+ const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => {
+ result[bid.impid - 1] = bid;
+ return result;
+ }, []);
+
+ return bids.map((bid, id) => {
+ const bidResponse = bidResponses[id];
+ if (bidResponse) {
+ const bidObject = {
+ requestId: bid.bidId,
+ cpm: bidResponse.price,
+ creativeId: bidResponse.crid,
+ ttl: 360,
+ netRevenue: true,
+ currency: cur,
+ mediaType: BANNER,
+ ad: bidResponse.adm,
+ width: bidResponse.w,
+ height: bidResponse.h,
+ meta: { advertiserDomains: bid.adomain ? bid.adomain : [] }
+ };
+ return bidObject;
+ }
+ }).filter(Boolean);
+ },
+
+ /**
+ * Register the user sync pixels which should be dropped after the auction.
+ *
+ * @param {SyncOptions} syncOptions Which user syncs are allowed?
+ * @param {ServerResponse[]} serverResponses List of server's responses.
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ return [];
+ }
+
+}
+registerBidder(spec);
+
+function getDomainFromURL(url) {
+ let anchor = document.createElement('a');
+ anchor.href = url;
+ return anchor.hostname;
+}
+
+function getDeviceType() {
+ if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) {
+ return 5; // 'tablet'
+ }
+ if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) {
+ return 4; // 'mobile'
+ }
+ return 2; // 'desktop'
+}
+
+function setOnAny(collection, key) {
+ for (let i = 0, result; i < collection.length; i++) {
+ result = deepAccess(collection[i], key);
+ if (result) {
+ return result;
+ }
+ }
+}
+
+function flatten(arr) {
+ return [].concat(...arr);
+}
+
+/* Turn bid request sizes into ut-compatible format */
+function transformSizes(requestSizes) {
+ if (!isArray(requestSizes)) {
+ return [];
+ }
+
+ if (requestSizes.length === 2 && !isArray(requestSizes[0])) {
+ return [{
+ w: parseInt(requestSizes[0], 10),
+ h: parseInt(requestSizes[1], 10)
+ }];
+ } else if (isArray(requestSizes[0])) {
+ return requestSizes.map(item =>
+ ({
+ w: parseInt(item[0], 10),
+ h: parseInt(item[1], 10)
+ })
+ );
+ }
+
+ return [];
+}
diff --git a/modules/codefuelBidAdapter.md b/modules/codefuelBidAdapter.md
new file mode 100644
index 00000000000..4e1a0dd6409
--- /dev/null
+++ b/modules/codefuelBidAdapter.md
@@ -0,0 +1,111 @@
+# Overview
+
+```
+Module Name: Codefuel Adapter
+Module Type: Bidder Adapter
+Maintainer: hayimm@codefuel.com
+```
+
+# Description
+
+Module that connects to Codefuel bidder to fetch bids.
+Display format is supported but not native format. Using OpenRTB standard.
+
+# Configuration
+
+## Bidder and usersync URLs
+
+The Codefuel adapter does not work without setting the correct bidder.
+You will receive the URLs when contacting us.
+
+```
+pbjs.setConfig({
+ codefuel: {
+ bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid',
+ usersyncUrl: 'https://usersync-url.com'
+ }
+});
+```
+
+
+# Test Native Parameters
+```
+ var adUnits = [
+ code: '/19968336/prebid_native_example_1',
+ mediaTypes: {
+ native: {
+ image: {
+ required: false,
+ sizes: [100, 50]
+ },
+ title: {
+ required: false,
+ len: 140
+ },
+ sponsoredBy: {
+ required: false
+ },
+ clickUrl: {
+ required: false
+ },
+ body: {
+ required: false
+ },
+ icon: {
+ required: false,
+ sizes: [50, 50]
+ }
+ }
+ },
+ bids: [{
+ bidder: 'codefuel',
+ params: {
+ publisher: {
+ id: '2706', // required
+ name: 'Publishers Name',
+ domain: 'publisher.com'
+ },
+ tagid: 'tag-id',
+ bcat: ['IAB1-1'],
+ badv: ['example.com']
+ }
+ }]
+ ];
+
+ pbjs.setConfig({
+ codefuel: {
+ bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid'
+ }
+ });
+```
+
+# Test Display Parameters
+```
+ var adUnits = [
+ code: '/19968336/prebid_display_example_1',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ bids: [{
+ bidder: 'codefuel',
+ params: {
+ publisher: {
+ id: '2706', // required
+ name: 'Publishers Name',
+ domain: 'publisher.com'
+ },
+ tagid: 'tag-id',
+ bcat: ['IAB1-1'],
+ badv: ['example.com']
+ },
+ }]
+ ];
+
+ pbjs.setConfig({
+ codefuel: {
+ bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid'
+ }
+ });
+```
diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js
index 5e7c58f28ad..72df7c7b465 100644
--- a/modules/colossussspBidAdapter.js
+++ b/modules/colossussspBidAdapter.js
@@ -1,6 +1,7 @@
import { getWindowTop, deepAccess, logMessage } from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { ajax } from '../src/ajax.js';
const BIDDER_CODE = 'colossusssp';
const G_URL = 'https://colossusssp.com/?c=o&m=multi';
@@ -46,7 +47,10 @@ export const spec = {
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: (bid) => {
- return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placement_id));
+ const validPlacamentId = bid.params && !isNaN(bid.params.placement_id);
+ const validGroupId = bid.params && !isNaN(bid.params.group_id);
+
+ return Boolean(bid.bidId && (validPlacamentId || validGroupId));
},
/**
@@ -60,13 +64,13 @@ export const spec = {
const location = winTop.location;
let placements = [];
let request = {
- 'deviceWidth': winTop.screen.width,
- 'deviceHeight': winTop.screen.height,
- 'language': (navigator && navigator.language) ? navigator.language : '',
- 'secure': location.protocol === 'https:' ? 1 : 0,
- 'host': location.host,
- 'page': location.pathname,
- 'placements': placements,
+ deviceWidth: winTop.screen.width,
+ deviceHeight: winTop.screen.height,
+ language: (navigator && navigator.language) ? navigator.language : '',
+ secure: location.protocol === 'https:' ? 1 : 0,
+ host: location.host,
+ page: location.pathname,
+ placements: placements,
};
if (bidderRequest) {
@@ -84,6 +88,7 @@ export const spec = {
let traff = bid.params.traffic || BANNER
let placement = {
placementId: bid.params.placement_id,
+ groupId: bid.params.group_id,
bidId: bid.bidId,
sizes: bid.mediaTypes[traff].sizes,
traffic: traff,
@@ -175,6 +180,12 @@ export const spec = {
type: 'image',
url: G_URL_SYNC
}];
+ },
+
+ onBidWon: (bid) => {
+ if (bid.nurl) {
+ ajax(bid.nurl, null);
+ }
}
};
diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md
index 8797c648c95..4187dfbf36e 100644
--- a/modules/colossussspBidAdapter.md
+++ b/modules/colossussspBidAdapter.md
@@ -26,5 +26,19 @@ Module that connects to Colossus SSP demand sources
traffic: 'banner'
}
}]
- ];
+ }, {
+ code: 'placementid_1',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300,600]]
+ }
+ },
+ bids: [{
+ bidder: 'colossusssp',
+ params: {
+ group_id: 0,
+ traffic: 'banner'
+ }
+ }]
+ }];
```
diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js
new file mode 100644
index 00000000000..77f918276bc
--- /dev/null
+++ b/modules/compassBidAdapter.js
@@ -0,0 +1,208 @@
+import { isFn, deepAccess, logMessage, logError } from '../src/utils.js';
+
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
+
+const BIDDER_CODE = 'compass';
+const AD_URL = 'https://sa-lb.deliverimp.com/pbjs';
+const SYNC_URL = 'https://sa-cs.deliverimp.com';
+
+function isBidResponseValid(bid) {
+ if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) {
+ return false;
+ }
+
+ switch (bid.mediaType) {
+ case BANNER:
+ return Boolean(bid.width && bid.height && bid.ad);
+ case VIDEO:
+ return Boolean(bid.vastUrl || bid.vastXml);
+ case NATIVE:
+ return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length);
+ default:
+ return false;
+ }
+}
+
+function getPlacementReqData(bid) {
+ const { params, bidId, mediaTypes } = bid;
+ const schain = bid.schain || {};
+ const { placementId, endpointId } = params;
+ const bidfloor = getBidFloor(bid);
+
+ const placement = {
+ bidId,
+ schain,
+ bidfloor
+ };
+
+ if (placementId) {
+ placement.placementId = placementId;
+ placement.type = 'publisher';
+ } else if (endpointId) {
+ placement.endpointId = endpointId;
+ placement.type = 'network';
+ }
+
+ if (mediaTypes && mediaTypes[BANNER]) {
+ placement.adFormat = BANNER;
+ placement.sizes = mediaTypes[BANNER].sizes;
+ } else if (mediaTypes && mediaTypes[VIDEO]) {
+ placement.adFormat = VIDEO;
+ placement.playerSize = mediaTypes[VIDEO].playerSize;
+ placement.minduration = mediaTypes[VIDEO].minduration;
+ placement.maxduration = mediaTypes[VIDEO].maxduration;
+ placement.mimes = mediaTypes[VIDEO].mimes;
+ placement.protocols = mediaTypes[VIDEO].protocols;
+ placement.startdelay = mediaTypes[VIDEO].startdelay;
+ placement.placement = mediaTypes[VIDEO].placement;
+ placement.skip = mediaTypes[VIDEO].skip;
+ placement.skipafter = mediaTypes[VIDEO].skipafter;
+ placement.minbitrate = mediaTypes[VIDEO].minbitrate;
+ placement.maxbitrate = mediaTypes[VIDEO].maxbitrate;
+ placement.delivery = mediaTypes[VIDEO].delivery;
+ placement.playbackmethod = mediaTypes[VIDEO].playbackmethod;
+ placement.api = mediaTypes[VIDEO].api;
+ placement.linearity = mediaTypes[VIDEO].linearity;
+ } else if (mediaTypes && mediaTypes[NATIVE]) {
+ placement.native = mediaTypes[NATIVE];
+ placement.adFormat = NATIVE;
+ }
+
+ return placement;
+}
+
+function getBidFloor(bid) {
+ if (!isFn(bid.getFloor)) {
+ return deepAccess(bid, 'params.bidfloor', 0);
+ }
+
+ try {
+ const bidFloor = bid.getFloor({
+ currency: 'USD',
+ mediaType: '*',
+ size: '*',
+ });
+ return bidFloor.floor;
+ } catch (err) {
+ logError(err);
+ return 0;
+ }
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
+
+ isBidRequestValid: (bid = {}) => {
+ const { params, bidId, mediaTypes } = bid;
+ let valid = Boolean(bidId && params && (params.placementId || params.endpointId));
+
+ if (mediaTypes && mediaTypes[BANNER]) {
+ valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes);
+ } else if (mediaTypes && mediaTypes[VIDEO]) {
+ valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize);
+ } else if (mediaTypes && mediaTypes[NATIVE]) {
+ valid = valid && Boolean(mediaTypes[NATIVE]);
+ } else {
+ valid = false;
+ }
+ return valid;
+ },
+
+ buildRequests: (validBidRequests = [], bidderRequest = {}) => {
+ let deviceWidth = 0;
+ let deviceHeight = 0;
+
+ let winLocation;
+ try {
+ const winTop = window.top;
+ deviceWidth = winTop.screen.width;
+ deviceHeight = winTop.screen.height;
+ winLocation = winTop.location;
+ } catch (e) {
+ logMessage(e);
+ winLocation = window.location;
+ }
+
+ const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer;
+ let refferLocation;
+ try {
+ refferLocation = refferUrl && new URL(refferUrl);
+ } catch (e) {
+ logMessage(e);
+ }
+
+ let location = refferLocation || winLocation;
+ const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : '';
+ const host = location.host;
+ const page = location.pathname;
+ const secure = location.protocol === 'https:' ? 1 : 0;
+ const placements = [];
+ const request = {
+ deviceWidth,
+ deviceHeight,
+ language,
+ secure,
+ host,
+ page,
+ placements,
+ coppa: config.getConfig('coppa') === true ? 1 : 0,
+ ccpa: bidderRequest.uspConsent || undefined,
+ gdpr: bidderRequest.gdprConsent || undefined,
+ tmax: config.getConfig('bidderTimeout')
+ };
+
+ const len = validBidRequests.length;
+ for (let i = 0; i < len; i++) {
+ const bid = validBidRequests[i];
+ placements.push(getPlacementReqData(bid));
+ }
+
+ return {
+ method: 'POST',
+ url: AD_URL,
+ data: request
+ };
+ },
+
+ interpretResponse: (serverResponse) => {
+ let response = [];
+ for (let i = 0; i < serverResponse.body.length; i++) {
+ let resItem = serverResponse.body[i];
+ if (isBidResponseValid(resItem)) {
+ const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : [];
+ resItem.meta = { ...resItem.meta, advertiserDomains };
+
+ response.push(resItem);
+ }
+ }
+ return response;
+ },
+
+ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
+ let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image';
+ let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`;
+ if (gdprConsent && gdprConsent.consentString) {
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
+ } else {
+ syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
+ }
+ }
+ if (uspConsent && uspConsent.consentString) {
+ syncUrl += `&ccpa_consent=${uspConsent.consentString}`;
+ }
+
+ const coppa = config.getConfig('coppa') ? 1 : 0;
+ syncUrl += `&coppa=${coppa}`;
+
+ return [{
+ type: syncType,
+ url: syncUrl
+ }];
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/compassBidAdapter.md b/modules/compassBidAdapter.md
new file mode 100644
index 00000000000..18d52c12384
--- /dev/null
+++ b/modules/compassBidAdapter.md
@@ -0,0 +1,79 @@
+# Overview
+
+```
+Module Name: Compass Bidder Adapter
+Module Type: Compass Bidder Adapter
+Maintainer: sa-support@brightcom.com
+```
+
+# Description
+
+Connects to Compass exchange for bids.
+Compass bid adapter supports Banner, Video (instream and outstream) and Native.
+
+# Test Parameters
+```
+ var adUnits = [
+ // Will return static test banner
+ {
+ code: 'adunit1',
+ mediaTypes: {
+ banner: {
+ sizes: [ [300, 250], [320, 50] ],
+ }
+ },
+ bids: [
+ {
+ bidder: 'compass',
+ params: {
+ placementId: 'testBanner',
+ }
+ }
+ ]
+ },
+ {
+ code: 'addunit2',
+ mediaTypes: {
+ video: {
+ playerSize: [ [640, 480] ],
+ context: 'instream',
+ minduration: 5,
+ maxduration: 60,
+ }
+ },
+ bids: [
+ {
+ bidder: 'compass',
+ params: {
+ placementId: 'testVideo',
+ }
+ }
+ ]
+ },
+ {
+ code: 'addunit3',
+ mediaTypes: {
+ native: {
+ title: {
+ required: true
+ },
+ body: {
+ required: true
+ },
+ icon: {
+ required: true,
+ size: [64, 64]
+ }
+ }
+ },
+ bids: [
+ {
+ bidder: 'compass',
+ params: {
+ placementId: 'testNative',
+ }
+ }
+ ]
+ }
+ ];
+```
\ No newline at end of file
diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js
new file mode 100644
index 00000000000..ac44a8b5a2d
--- /dev/null
+++ b/modules/connectIdSystem.js
@@ -0,0 +1,104 @@
+/**
+ * This module adds support for Yahoo ConnectID to the user ID module system.
+ * The {@link module:modules/userId} module is required
+ * @module modules/connectIdSystem
+ * @requires module:modules/userId
+ */
+
+import {ajax} from '../src/ajax.js';
+import {submodule} from '../src/hook.js';
+import {logError, formatQS} from '../src/utils.js';
+import includes from 'core-js-pure/features/array/includes.js';
+
+const MODULE_NAME = 'connectId';
+const VENDOR_ID = 25;
+const PLACEHOLDER = '__PIXEL_ID__';
+const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`;
+
+function isEUConsentRequired(consentData) {
+ return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies);
+}
+
+/** @type {Submodule} */
+export const connectIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: MODULE_NAME,
+ /**
+ * @type {Number}
+ */
+ gvlid: VENDOR_ID,
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function
+ * @returns {{connectId: string} | undefined}
+ */
+ decode(value) {
+ return (typeof value === 'object' && value.connectid)
+ ? {connectId: value.connectid} : undefined;
+ },
+ /**
+ * Gets the Yahoo ConnectID
+ * @function
+ * @param {SubmoduleConfig} [config]
+ * @param {ConsentData} [consentData]
+ * @returns {IdResponse|undefined}
+ */
+ getId(config, consentData) {
+ const params = config.params || {};
+ if (!params || typeof params.he !== 'string' ||
+ (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) {
+ logError('The connectId submodule requires the \'he\' and \'pixelId\' parameters to be defined.');
+ return;
+ }
+
+ const data = {
+ '1p': includes([1, '1', true], params['1p']) ? '1' : '0',
+ he: params.he,
+ gdpr: isEUConsentRequired(consentData) ? '1' : '0',
+ gdpr_consent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '',
+ us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : ''
+ };
+
+ if (params.pixelId) {
+ data.pixelId = params.pixelId
+ }
+
+ const resp = function (callback) {
+ const callbacks = {
+ success: response => {
+ let responseObj;
+ if (response) {
+ try {
+ responseObj = JSON.parse(response);
+ } catch (error) {
+ logError(error);
+ }
+ }
+ callback(responseObj);
+ },
+ error: error => {
+ logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
+ callback();
+ }
+ };
+ const endpoint = UPS_ENDPOINT.replace(PLACEHOLDER, params.pixelId);
+ let url = `${params.endpoint || endpoint}?${formatQS(data)}`;
+ connectIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true});
+ };
+ return {callback: resp};
+ },
+
+ /**
+ * Return the function used to perform XHR calls.
+ * Utilised for each of testing.
+ * @returns {Function}
+ */
+ getAjaxFn() {
+ return ajax;
+ }
+};
+
+submodule('userId', connectIdSubmodule);
diff --git a/modules/connectIdSystem.md b/modules/connectIdSystem.md
new file mode 100644
index 00000000000..f2153e1b6cb
--- /dev/null
+++ b/modules/connectIdSystem.md
@@ -0,0 +1,33 @@
+## Yahoo ConnectID User ID Submodule
+
+Yahoo ConnectID user ID Module.
+
+### Prebid Params
+
+```
+pbjs.setConfig({
+ userSync: {
+ userIds: [{
+ name: 'connectId',
+ storage: {
+ name: 'connectId',
+ type: 'html5',
+ expires: 15
+ },
+ params: {
+ pixelId: 58776,
+ he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a'
+ }
+ }]
+ }
+});
+```
+## Parameter Descriptions for the `usersync` Configuration Section
+The below parameters apply only to the Yahoo ConnectID user ID Module.
+
+| Param under usersync.userIds[] | Scope | Type | Description | Example |
+| --- | --- | --- | --- | --- |
+| name | Required | String | ID value for the Yahoo ConnectID module - `"connectId"` | `"connectId"` |
+| params | Required | Object | Data for Yahoo ConnectID initialization. | |
+| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id | `8976` |
+| params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` |
diff --git a/modules/consentManagement.js b/modules/consentManagement.js
index 65ffc9a4def..ecd0c0eec4b 100644
--- a/modules/consentManagement.js
+++ b/modules/consentManagement.js
@@ -371,7 +371,13 @@ function processCmpData(consentObject, hookConfig) {
* General timeout callback when interacting with CMP takes too long.
*/
function cmpTimedOut(hookConfig) {
- cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig);
+ if (cmpVersion === 2) {
+ logWarn(`No response from CMP, continuing auction...`)
+ storeConsentData(undefined);
+ exitModule(null, hookConfig)
+ } else {
+ cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig);
+ }
}
/**
diff --git a/modules/consumableBidAdapter.md b/modules/consumableBidAdapter.md
index 2189494ebd4..ba472899c49 100644
--- a/modules/consumableBidAdapter.md
+++ b/modules/consumableBidAdapter.md
@@ -4,7 +4,7 @@ Module Name: Consumable Bid Adapter
Module Type: Consumable Adapter
-Maintainer: naffis@consumable.com
+Maintainer: prebid@consumable.com
# Description
diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js
index b98b72b59ad..812ec53d686 100644
--- a/modules/craftBidAdapter.js
+++ b/modules/craftBidAdapter.js
@@ -5,6 +5,7 @@ import { auctionManager } from '../src/auctionManager.js';
import find from 'core-js-pure/features/array/find.js';
import includes from 'core-js-pure/features/array/includes.js';
import { getStorageManager } from '../src/storageManager.js';
+import {ajax} from '../src/ajax.js';
const BIDDER_CODE = 'craft';
const URL_BASE = 'https://gacraft.jp/prebid-v3';
@@ -110,9 +111,10 @@ export const spec = {
},
onBidWon: function(bid) {
- var xhr = new XMLHttpRequest();
- xhr.open('POST', bid._prebidWon);
- xhr.send();
+ ajax(bid._prebidWon, null, null, {
+ method: 'POST',
+ contentType: 'application/json'
+ });
}
};
diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js
index a4ec99e4fa8..0285ab6be5b 100644
--- a/modules/criteoBidAdapter.js
+++ b/modules/criteoBidAdapter.js
@@ -24,7 +24,7 @@ const LOG_PREFIX = 'Criteo: ';
Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js
*/
const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%';
-export const FAST_BID_VERSION_CURRENT = 113;
+export const FAST_BID_VERSION_CURRENT = 117;
const FAST_BID_VERSION_LATEST = 'latest';
const FAST_BID_VERSION_NONE = 'none';
const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js';
@@ -281,6 +281,7 @@ function checkNativeSendId(bidRequest) {
*/
function buildCdbRequest(context, bidRequests, bidderRequest) {
let networkId;
+ let schain;
const request = {
publisher: {
url: context.url,
@@ -288,6 +289,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) {
},
slots: bidRequests.map(bidRequest => {
networkId = bidRequest.params.networkId || networkId;
+ schain = bidRequest.schain || schain;
const slot = {
impid: bidRequest.adUnitCode,
transactionid: bidRequest.transactionId,
@@ -344,6 +346,13 @@ function buildCdbRequest(context, bidRequests, bidderRequest) {
if (networkId) {
request.publisher.networkid = networkId;
}
+ if (schain) {
+ request.source = {
+ ext: {
+ schain: schain
+ }
+ }
+ };
request.user = {
ext: bidderRequest.userExt
};
diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js
index c716a3c9cd6..ecf7b3aaac4 100644
--- a/modules/criteoIdSystem.js
+++ b/modules/criteoIdSystem.js
@@ -33,15 +33,37 @@ function getFromAllStorages(key) {
return storage.getCookie(key) || storage.getDataFromLocalStorage(key);
}
-function saveOnAllStorages(key, value) {
+function saveOnAllStorages(key, value, hostname) {
if (key && value) {
- storage.setCookie(key, value, expirationString);
storage.setDataInLocalStorage(key, value);
+ setCookieOnAllDomains(key, value, expirationString, hostname, true);
}
}
-function deleteFromAllStorages(key) {
- storage.setCookie(key, '', pastDateString);
+function setCookieOnAllDomains(key, value, expiration, hostname, stopOnSuccess) {
+ const subDomains = hostname.split('.');
+ for (let i = 0; i < subDomains.length; ++i) {
+ // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1)
+ const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.');
+
+ try {
+ storage.setCookie(key, value, expiration, null, '.' + domain);
+
+ if (stopOnSuccess) {
+ // Try to read the cookie to check if we wrote it
+ const ck = storage.getCookie(key);
+ if (ck && ck === value) {
+ break;
+ }
+ }
+ } catch (error) {
+
+ }
+ }
+}
+
+function deleteFromAllStorages(key, hostname) {
+ setCookieOnAllDomains(key, '', pastDateString, hostname, true);
storage.removeDataFromLocalStorage(key);
}
@@ -89,15 +111,15 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) {
const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl;
urlsToCall.forEach(url => triggerPixel(url));
} else if (jsonResponse.bundle) {
- saveOnAllStorages(bundleStorageKey, jsonResponse.bundle);
+ saveOnAllStorages(bundleStorageKey, jsonResponse.bundle, domain);
}
if (jsonResponse.bidId) {
- saveOnAllStorages(bididStorageKey, jsonResponse.bidId);
+ saveOnAllStorages(bididStorageKey, jsonResponse.bidId, domain);
const criteoId = { criteoId: jsonResponse.bidId };
callback(criteoId);
} else {
- deleteFromAllStorages(bididStorageKey);
+ deleteFromAllStorages(bididStorageKey, domain);
callback();
}
},
diff --git a/modules/currency.js b/modules/currency.js
index dc77ee21430..5f27e49798a 100644
--- a/modules/currency.js
+++ b/modules/currency.js
@@ -1,7 +1,7 @@
import { logInfo, logWarn, logError, logMessage } from '../src/utils.js';
import { getGlobal } from '../src/prebidGlobal.js';
import { createBid } from '../src/bidfactory.js';
-import { STATUS } from '../src/constants.json';
+import CONSTANTS from '../src/constants.json';
import { ajax } from '../src/ajax.js';
import { config } from '../src/config.js';
import { getHook } from '../src/hook.js';
@@ -20,6 +20,25 @@ export var currencyRates = {};
var bidderCurrencyDefault = {};
var defaultRates;
+export const ready = (() => {
+ let isDone, resolver, promise;
+ function reset() {
+ isDone = false;
+ resolver = null;
+ promise = new Promise((resolve) => {
+ resolver = resolve;
+ if (isDone) resolve();
+ })
+ }
+ function done() {
+ isDone = true;
+ if (resolver != null) { resolver() }
+ }
+ reset();
+
+ return {done, reset, promise: () => promise}
+})();
+
/**
* Configuration function for currency
* @param {string} [config.adServerCurrency = 'USD']
@@ -138,11 +157,15 @@ function initCurrency(url) {
logInfo('currencyRates set to ' + JSON.stringify(currencyRates));
currencyRatesLoaded = true;
processBidResponseQueue();
+ ready.done();
} catch (e) {
errorSettingsRates('Failed to parse currencyRates response: ' + response);
}
},
- error: errorSettingsRates
+ error: function (...args) {
+ errorSettingsRates(...args);
+ ready.done();
+ }
}
);
}
@@ -197,6 +220,8 @@ export function addBidResponseHook(fn, adUnitCode, bid) {
bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid]));
if (!currencySupportEnabled || currencyRatesLoaded) {
processBidResponseQueue();
+ } else {
+ fn.bail(ready.promise());
}
}
@@ -219,7 +244,7 @@ function wrapFunction(fn, context, params) {
}
} catch (e) {
logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e);
- params[1] = createBid(STATUS.NO_BID, {
+ params[1] = createBid(CONSTANTS.STATUS.NO_BID, {
bidder: bid.bidderCode || bid.bidder,
bidId: bid.requestId
});
diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js
new file mode 100644
index 00000000000..c9caa78e5e7
--- /dev/null
+++ b/modules/cwireBidAdapter.js
@@ -0,0 +1,275 @@
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import { getRefererInfo } from '../src/refererDetection.js';
+import { getStorageManager } from '../src/storageManager.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import { OUTSTREAM } from '../src/video.js';
+import {
+ isArray,
+ isNumber,
+ generateUUID,
+ parseSizesInput,
+ deepAccess,
+ getParameterByName,
+ getValue,
+ getBidIdParameter,
+ logError,
+ logWarn,
+} from '../src/utils.js';
+import { Renderer } from '../src/Renderer.js';
+import find from 'core-js-pure/features/array/find.js';
+
+// ------------------------------------
+const BIDDER_CODE = 'cwire';
+export const ENDPOINT_URL = 'https://embed.cwi.re/delivery/prebid';
+export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.min.js';
+// ------------------------------------
+export const CW_PAGE_VIEW_ID = generateUUID();
+const LS_CWID_KEY = 'cw_cwid';
+const CW_GROUPS_QUERY = 'cwgroups';
+const CW_CREATIVE_QUERY = 'cwcreative';
+
+const storage = getStorageManager();
+
+/**
+ * ------------------------------------
+ * ------------------------------------
+ * @param bid
+ * @returns {Array}
+ */
+export function getSlotSizes(bid) {
+ return parseSizesInput(getAllMediaSizes(bid));
+}
+
+/**
+ * ------------------------------------
+ * ------------------------------------
+ * @param bid
+ * @returns {*[]}
+ */
+export function getAllMediaSizes(bid) {
+ let playerSizes = deepAccess(bid, 'mediaTypes.video.playerSize');
+ let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes');
+ let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes');
+
+ const sizes = [];
+
+ if (isArray(playerSizes)) {
+ playerSizes.forEach((s) => {
+ sizes.push(s);
+ })
+ }
+
+ if (isArray(videoSizes)) {
+ videoSizes.forEach((s) => {
+ sizes.push(s);
+ })
+ }
+
+ if (isArray(bannerSizes)) {
+ bannerSizes.forEach((s) => {
+ sizes.push(s);
+ })
+ }
+ return sizes;
+}
+
+const getQueryVariable = (variable) => {
+ let value = getParameterByName(variable);
+ if (value === '') {
+ value = null;
+ }
+ return value;
+};
+
+/**
+ * ------------------------------------
+ * ------------------------------------
+ * @param validBidRequests
+ * @returns {*[]}
+ */
+export const mapSlotsData = function(validBidRequests) {
+ const slots = [];
+ validBidRequests.forEach(bid => {
+ const bidObj = {};
+ // get the pacement and page ids
+ let placementId = getValue(bid.params, 'placementId');
+ let pageId = getValue(bid.params, 'pageId');
+ let adUnitElementId = getValue(bid.params, 'adUnitElementId');
+ // get the rest of the auction/bid/transaction info
+ bidObj.auctionId = getBidIdParameter('auctionId', bid);
+ bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid);
+ bidObj.adUnitElementId = adUnitElementId;
+ bidObj.bidId = getBidIdParameter('bidId', bid);
+ bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid);
+ bidObj.placementId = placementId;
+ bidObj.pageId = pageId;
+ bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid);
+ bidObj.transactionId = getBidIdParameter('transactionId', bid);
+ bidObj.sizes = getSlotSizes(bid);
+ slots.push(bidObj);
+ });
+
+ return slots;
+};
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO],
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function(bid) {
+ bid.params = bid.params || {};
+
+ // if ad unit elemt id not provided - use adUnitCode by default
+ if (!bid.params.adUnitElementId) {
+ bid.params.adUnitElementId = bid.code;
+ }
+
+ if (!bid.params.placementId || !isNumber(bid.params.placementId)) {
+ logError('placementId not provided or invalid');
+ return false;
+ }
+
+ if (!bid.params.pageId || !isNumber(bid.params.pageId)) {
+ logError('pageId not provided');
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * ------------------------------------
+ * Make a server request from the
+ * list of BidRequests.
+ * ------------------------------------
+ * @param {validBidRequests[]} - an array of bids
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function(validBidRequests, bidderRequest) {
+ let slots = [];
+ let referer;
+ try {
+ referer = getRefererInfo().referer;
+ slots = mapSlotsData(validBidRequests);
+ } catch (e) {
+ logWarn(e);
+ }
+
+ let refgroups = [];
+
+ const cwCreativeId = getQueryVariable(CW_CREATIVE_QUERY);
+ const rgQuery = getQueryVariable(CW_GROUPS_QUERY);
+ if (rgQuery !== null) {
+ refgroups = rgQuery.split(',');
+ }
+
+ const localStorageCWID = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(LS_CWID_KEY) : null;
+
+ const payload = {
+ cwid: localStorageCWID,
+ refgroups,
+ cwcreative: cwCreativeId,
+ slots: slots,
+ httpRef: referer || '',
+ pageViewId: CW_PAGE_VIEW_ID,
+ };
+
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: payload
+ };
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function(serverResponse, bidRequest) {
+ const bidResponses = [];
+
+ try {
+ if (typeof bidRequest.data === 'string') {
+ bidRequest.data = JSON.parse(bidRequest.data);
+ }
+ const serverBody = serverResponse.body;
+ serverBody.bids.forEach((br) => {
+ const bidReq = find(bidRequest.data.slots, bid => bid.bidId === br.requestId);
+
+ let mediaType = BANNER;
+
+ const bidResponse = {
+ requestId: br.requestId,
+ cpm: br.cpm,
+ bidderCode: BIDDER_CODE,
+ width: br.dimensions[0],
+ height: br.dimensions[1],
+ creativeId: br.creativeId,
+ currency: br.currency,
+ netRevenue: br.netRevenue,
+ ttl: br.ttl,
+ meta: {
+ advertiserDomains: br.adomains ? br.advertiserDomains : [],
+ },
+
+ };
+
+ // ------------------------------------
+ // IF BANNER
+ // ------------------------------------
+
+ if (deepAccess(bidReq, 'mediaTypes.banner')) {
+ bidResponse.ad = br.html;
+ }
+ // ------------------------------------
+ // IF VIDEO
+ // ------------------------------------
+ if (deepAccess(bidReq, 'mediaTypes.video')) {
+ mediaType = VIDEO;
+ bidResponse.vastXml = br.vastXml;
+ bidResponse.videoScript = br.html;
+ const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context');
+ if (mediaTypeContext === OUTSTREAM) {
+ const r = Renderer.install({
+ id: bidResponse.requestId,
+ adUnitCode: bidReq.adUnitCode,
+ url: RENDERER_URL,
+ loaded: false,
+ config: {
+ ...deepAccess(bidReq, 'mediaTypes.video'),
+ ...deepAccess(br, 'outstream', {})
+ }
+ });
+
+ // set renderer
+ try {
+ bidResponse.renderer = r;
+ bidResponse.renderer.setRender(function(bid) {
+ if (window.CWIRE && window.CWIRE.outstream) {
+ window.CWIRE.outstream.renderAd(bid);
+ }
+ });
+ } catch (err) {
+ logWarn('Prebid Error calling setRender on newRenderer', err);
+ }
+ }
+ }
+
+ bidResponse.mediaType = mediaType;
+ bidResponses.push(bidResponse);
+ });
+ } catch (e) {
+ logWarn(e);
+ }
+
+ return bidResponses;
+ },
+}
+registerBidder(spec);
diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md
new file mode 100644
index 00000000000..fc0889e05ad
--- /dev/null
+++ b/modules/cwireBidAdapter.md
@@ -0,0 +1,43 @@
+# Overview
+
+Module Name: C-WIRE Bid Adapter
+Module Type: Adagio Adapter
+Maintainer: dragan@cwire.ch
+
+## Description
+
+Connects to C-WIRE demand source to fetch bids.
+
+## Configuration
+
+
+Below, the list of C-WIRE params and where they can be set.
+
+| Param name | Global config | AdUnit config | Type | Required |
+| ---------- | ------------- | ------------- | ---- | ---------|
+| pageId | | x | number | YES |
+| placementId | | x | number | YES |
+| adUnitElementId | | x | string | NO |
+
+### adUnit configuration
+
+```javascript
+var adUnits = [
+ {
+ code: 'target_div_id', // REQUIRED
+ bids: [{
+ bidder: 'cwire',
+ mediaTypes: {
+ banner: {
+ sizes: [[1, 1]],
+ }
+ },
+ params: {
+ pageId: 1422, // required - number
+ placementId: 2211521, // required - number
+ adUnitElementId: 'other_div', // optional, div id to write to, if not set it will default to ad unit code
+ }
+ }]
+ }
+];
+```
\ No newline at end of file
diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js
new file mode 100644
index 00000000000..cdcc9f1d038
--- /dev/null
+++ b/modules/dailyhuntBidAdapter.js
@@ -0,0 +1,435 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import * as mediaTypes from '../src/mediaTypes.js';
+import {deepAccess, _map, isEmpty} from '../src/utils.js';
+import { ajax } from '../src/ajax.js';
+import find from 'core-js-pure/features/array/find.js';
+import { OUTSTREAM, INSTREAM } from '../src/video.js';
+
+const BIDDER_CODE = 'dailyhunt';
+const BIDDER_ALIAS = 'dh';
+const SUPPORTED_MEDIA_TYPES = [mediaTypes.BANNER, mediaTypes.NATIVE, mediaTypes.VIDEO];
+
+const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner=';
+const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner=';
+
+const ORTB_NATIVE_TYPE_MAPPING = {
+ img: {
+ '3': 'image',
+ '1': 'icon'
+ },
+ data: {
+ '1': 'sponsoredBy',
+ '2': 'body',
+ '3': 'rating',
+ '4': 'likes',
+ '5': 'downloads',
+ '6': 'price',
+ '7': 'salePrice',
+ '8': 'phone',
+ '9': 'address',
+ '10': 'body2',
+ '11': 'displayUrl',
+ '12': 'cta'
+ }
+}
+
+const ORTB_NATIVE_PARAMS = {
+ title: {
+ id: 0,
+ name: 'title'
+ },
+ icon: {
+ id: 1,
+ type: 1,
+ name: 'img'
+ },
+ image: {
+ id: 2,
+ type: 3,
+ name: 'img'
+ },
+ sponsoredBy: {
+ id: 3,
+ name: 'data',
+ type: 1
+ },
+ body: {
+ id: 4,
+ name: 'data',
+ type: 2
+ },
+ cta: {
+ id: 5,
+ type: 12,
+ name: 'data'
+ },
+ body2: {
+ id: 4,
+ name: 'data',
+ type: 10
+ },
+};
+
+// Encode URI.
+const _encodeURIComponent = function (a) {
+ let b = window.encodeURIComponent(a);
+ b = b.replace(/'/g, '%27');
+ return b;
+}
+
+// Extract key from collections.
+const extractKeyInfo = (collection, key) => {
+ for (let i = 0, result; i < collection.length; i++) {
+ result = deepAccess(collection[i].params, key);
+ if (result) {
+ return result;
+ }
+ }
+ return undefined
+}
+
+// Flattern Array.
+const flatten = (arr) => {
+ return [].concat(...arr);
+}
+
+const createOrtbRequest = (validBidRequests, bidderRequest) => {
+ let device = createOrtbDeviceObj(validBidRequests);
+ let user = createOrtbUserObj(validBidRequests)
+ let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.referer)
+ return {
+ id: bidderRequest.auctionId,
+ imp: [],
+ site,
+ device,
+ user,
+ };
+}
+
+const createOrtbDeviceObj = (validBidRequests) => {
+ let device = { ...extractKeyInfo(validBidRequests, `device`) };
+ device.ua = navigator.userAgent;
+ return device;
+}
+
+const createOrtbUserObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `user`) })
+
+const createOrtbSiteObj = (validBidRequests, page) => {
+ let site = { ...extractKeyInfo(validBidRequests, `site`), page };
+ let publisher = createOrtbPublisherObj(validBidRequests);
+ if (!site.publisher) {
+ site.publisher = publisher
+ }
+ return site
+}
+
+const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `publisher`) })
+
+// get bidFloor Function for different creatives
+function getBidFloor(bid, creative) {
+ let floorInfo = typeof (bid.getFloor) == 'function' ? bid.getFloor({ currency: 'USD', mediaType: creative, size: '*' }) : {};
+ return Math.floor(floorInfo.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0));
+}
+
+const createOrtbImpObj = (bid) => {
+ let params = bid.params
+ let testMode = !!bid.params.test_mode
+
+ // Validate Banner Request.
+ let bannerObj = deepAccess(bid.mediaTypes, `banner`);
+ let nativeObj = deepAccess(bid.mediaTypes, `native`);
+ let videoObj = deepAccess(bid.mediaTypes, `video`);
+
+ let imp = {
+ id: bid.bidId,
+ ext: {
+ dailyhunt: {
+ placement_id: params.placement_id,
+ publisher_id: params.publisher_id,
+ partner: params.partner_name
+ }
+ }
+ };
+
+ // Test Mode Campaign.
+ if (testMode) {
+ imp.ext.test_mode = testMode;
+ }
+
+ if (bannerObj) {
+ imp.banner = {
+ ...createOrtbImpBannerObj(bid, bannerObj)
+ }
+ imp.bidfloor = getBidFloor(bid, 'banner');
+ } else if (nativeObj) {
+ imp.native = {
+ ...createOrtbImpNativeObj(bid, nativeObj)
+ }
+ imp.bidfloor = getBidFloor(bid, 'native');
+ } else if (videoObj) {
+ imp.video = {
+ ...createOrtbImpVideoObj(bid, videoObj)
+ }
+ imp.bidfloor = getBidFloor(bid, 'video');
+ }
+ return imp;
+}
+
+const createOrtbImpBannerObj = (bid, bannerObj) => {
+ let format = [];
+ bannerObj.sizes.forEach(size => format.push({ w: size[0], h: size[1] }))
+
+ return {
+ id: 'banner-' + bid.bidId,
+ format
+ }
+}
+
+const createOrtbImpNativeObj = (bid, nativeObj) => {
+ const assets = _map(bid.nativeParams, (bidParams, key) => {
+ const props = ORTB_NATIVE_PARAMS[key];
+ const asset = {
+ required: bidParams.required & 1,
+ };
+ if (props) {
+ let h = 0;
+ let w = 0;
+
+ asset.id = props.id;
+
+ if (bidParams.sizes) {
+ const sizes = flatten(bidParams.sizes);
+ w = sizes[0];
+ h = sizes[1];
+ }
+
+ asset[props.name] = {
+ len: bidParams.len ? bidParams.len : 20,
+ type: props.type,
+ w,
+ h
+ };
+
+ return asset;
+ }
+ }).filter(Boolean);
+ let request = {
+ assets,
+ ver: '1,0'
+ }
+ return { request: JSON.stringify(request) };
+}
+
+const createOrtbImpVideoObj = (bid, videoObj) => {
+ let obj = {};
+ let params = bid.params
+ if (!isEmpty(bid.params.video)) {
+ obj = {
+ topframe: 1,
+ skip: params.video.skippable || 0,
+ linearity: params.video.linearity || 1,
+ minduration: params.video.minduration || 5,
+ maxduration: params.video.maxduration || 60,
+ mimes: params.video.mimes || ['video/mp4'],
+ protocols: getProtocols(params.video),
+ w: params.video.playerSize[0][0],
+ h: params.video.playerSize[0][1],
+ };
+ } else {
+ obj = {
+ mimes: ['video/mp4'],
+ };
+ }
+ obj.ext = {
+ ...videoObj,
+ }
+ return obj;
+}
+
+export function getProtocols({protocols}) {
+ let defaultValue = [2, 3, 5, 6, 7, 8];
+ let listProtocols = [
+ {key: 'VAST_1_0', value: 1},
+ {key: 'VAST_2_0', value: 2},
+ {key: 'VAST_3_0', value: 3},
+ {key: 'VAST_1_0_WRAPPER', value: 4},
+ {key: 'VAST_2_0_WRAPPER', value: 5},
+ {key: 'VAST_3_0_WRAPPER', value: 6},
+ {key: 'VAST_4_0', value: 7},
+ {key: 'VAST_4_0_WRAPPER', value: 8}
+ ];
+ if (protocols) {
+ return listProtocols.filter(p => {
+ return protocols.indexOf(p.key) !== -1
+ }).map(p => p.value);
+ } else {
+ return defaultValue;
+ }
+}
+
+const createServerRequest = (ortbRequest, validBidRequests, isTestMode = 'false') => ({
+ method: 'POST',
+ url: isTestMode === 'true' ? PROD_PREBID_TEST_ENDPOINT_URL + validBidRequests[0].params.partner_name : PROD_PREBID_ENDPOINT_URL + validBidRequests[0].params.partner_name,
+ data: JSON.stringify(ortbRequest),
+ options: {
+ contentType: 'application/json',
+ withCredentials: true
+ },
+ bids: validBidRequests
+})
+
+const createPrebidBannerBid = (bid, bidResponse) => ({
+ requestId: bid.bidId,
+ cpm: bidResponse.price.toFixed(2),
+ creativeId: bidResponse.crid,
+ width: bidResponse.w,
+ height: bidResponse.h,
+ ttl: 360,
+ netRevenue: bid.netRevenue === 'net',
+ currency: 'USD',
+ ad: bidResponse.adm,
+ mediaType: 'banner',
+ winUrl: bidResponse.nurl,
+ adomain: bidResponse.adomain
+})
+
+const createPrebidNativeBid = (bid, bidResponse) => ({
+ requestId: bid.bidId,
+ cpm: bidResponse.price.toFixed(2),
+ creativeId: bidResponse.crid,
+ currency: 'USD',
+ ttl: 360,
+ netRevenue: bid.netRevenue === 'net',
+ native: parseNative(bidResponse),
+ mediaType: 'native',
+ winUrl: bidResponse.nurl,
+ width: bidResponse.w,
+ height: bidResponse.h,
+ adomain: bidResponse.adomain
+})
+
+const parseNative = (bid) => {
+ let adm = JSON.parse(bid.adm)
+ const { assets, link, imptrackers, jstracker } = adm.native;
+ const result = {
+ clickUrl: _encodeURIComponent(link.url),
+ clickTrackers: link.clicktrackers || [],
+ impressionTrackers: imptrackers || [],
+ javascriptTrackers: jstracker ? [ jstracker ] : []
+ };
+ assets.forEach(asset => {
+ if (!isEmpty(asset.title)) {
+ result.title = asset.title.text
+ } else if (!isEmpty(asset.img)) {
+ result[ORTB_NATIVE_TYPE_MAPPING.img[asset.img.type]] = {
+ url: asset.img.url,
+ height: asset.img.h,
+ width: asset.img.w
+ }
+ } else if (!isEmpty(asset.data)) {
+ result[ORTB_NATIVE_TYPE_MAPPING.data[asset.data.type]] = asset.data.value
+ }
+ });
+
+ return result;
+}
+
+const createPrebidVideoBid = (bid, bidResponse) => {
+ let videoBid = {
+ requestId: bid.bidId,
+ cpm: bidResponse.price.toFixed(2),
+ creativeId: bidResponse.crid,
+ width: bidResponse.w,
+ height: bidResponse.h,
+ ttl: 360,
+ netRevenue: bid.netRevenue === 'net',
+ currency: 'USD',
+ mediaType: 'video',
+ winUrl: bidResponse.nurl,
+ adomain: bidResponse.adomain
+ };
+
+ let videoContext = bid.mediaTypes.video.context;
+ switch (videoContext) {
+ case OUTSTREAM:
+ videoBid.vastXml = bidResponse.adm;
+ break;
+ case INSTREAM:
+ videoBid.videoCacheKey = bidResponse.ext.bidder.cacheKey;
+ videoBid.vastUrl = bidResponse.ext.bidder.vastUrl;
+ break;
+ }
+ return videoBid;
+}
+
+const getQueryVariable = (variable) => {
+ let query = window.location.search.substring(1);
+ let vars = query.split('&');
+ for (var i = 0; i < vars.length; i++) {
+ let pair = vars[i].split('=');
+ if (decodeURIComponent(pair[0]) == variable) {
+ return decodeURIComponent(pair[1]);
+ }
+ }
+ return false;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+
+ aliases: [BIDDER_ALIAS],
+
+ supportedMediaTypes: SUPPORTED_MEDIA_TYPES,
+
+ isBidRequestValid: bid => !!bid.params.placement_id && !!bid.params.publisher_id && !!bid.params.partner_name,
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ let serverRequests = [];
+
+ // ORTB Request.
+ let ortbReq = createOrtbRequest(validBidRequests, bidderRequest);
+
+ validBidRequests.forEach((bid) => {
+ let imp = createOrtbImpObj(bid)
+ ortbReq.imp.push(imp);
+ });
+
+ serverRequests.push({ ...createServerRequest(ortbReq, validBidRequests, getQueryVariable('dh_test')) });
+
+ return serverRequests;
+ },
+
+ interpretResponse: function (serverResponse, request) {
+ const { seatbid } = serverResponse.body;
+ let bids = request.bids;
+ let prebidResponse = [];
+
+ let seatBids = seatbid[0].bid;
+
+ seatBids.forEach(ortbResponseBid => {
+ let bidId = ortbResponseBid.impid;
+ let actualBid = find(bids, (bid) => bid.bidId === bidId);
+ let bidMediaType = ortbResponseBid.ext.prebid.type
+ switch (bidMediaType) {
+ case mediaTypes.BANNER:
+ prebidResponse.push(createPrebidBannerBid(actualBid, ortbResponseBid));
+ break;
+ case mediaTypes.NATIVE:
+ prebidResponse.push(createPrebidNativeBid(actualBid, ortbResponseBid));
+ break;
+ case mediaTypes.VIDEO:
+ prebidResponse.push(createPrebidVideoBid(actualBid, ortbResponseBid));
+ break;
+ }
+ })
+ return prebidResponse;
+ },
+
+ onBidWon: function(bid) {
+ ajax(bid.winUrl, null, null, {
+ method: 'GET'
+ })
+ }
+}
+
+registerBidder(spec);
diff --git a/modules/dailyhuntBidAdapter.md b/modules/dailyhuntBidAdapter.md
index acfd20a4de0..a08b66fb826 100644
--- a/modules/dailyhuntBidAdapter.md
+++ b/modules/dailyhuntBidAdapter.md
@@ -99,3 +99,7 @@ Dailyhunt bid adapter supports Banner, Native and Video.
}
];
```
+
+## latest commit has all the required support for latest version of prebid above 6.x
+## Dailyhunt adapter was there till 4.x and then removed in version 5.x of prebid.
+## this doc has been also submitted to https://github.com/prebid/prebid.github.io
\ No newline at end of file
diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js
index 197ba19b1d6..43039e070c3 100644
--- a/modules/datablocksBidAdapter.js
+++ b/modules/datablocksBidAdapter.js
@@ -94,7 +94,7 @@ export const spec = {
code: 'datablocks',
// DATABLOCKS SCOPED OBJECT
- db_obj: {metrics_host: 'prebid.datablocks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0},
+ db_obj: {metrics_host: 'prebid.dblks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0},
// STORE THE DATABLOCKS BUYERID IN STORAGE
store_dbid: function(dbid) {
@@ -388,12 +388,12 @@ export const spec = {
};
let sourceId = validRequests[0].params.source_id || 0;
- let host = validRequests[0].params.host || 'prebid.datablocks.net';
+ let host = validRequests[0].params.host || 'prebid.dblks.net';
// RETURN WITH THE REQUEST AND PAYLOAD
return {
method: 'POST',
- url: `https://${sourceId}.${host}/openrtb/?sid=${sourceId}`,
+ url: `https://${host}/openrtb/?sid=${sourceId}`,
data: {
id: bidderRequest.auctionId,
imp: imps,
diff --git a/modules/dchain.js b/modules/dchain.js
new file mode 100644
index 00000000000..6a1bd1ebf70
--- /dev/null
+++ b/modules/dchain.js
@@ -0,0 +1,149 @@
+import includes from 'core-js-pure/features/array/includes.js';
+import { config } from '../src/config.js';
+import { getHook } from '../src/hook.js';
+import { _each, isStr, isArray, isPlainObject, hasOwn, deepClone, deepAccess, logWarn, logError } from '../src/utils.js';
+
+const shouldBeAString = ' should be a string';
+const shouldBeAnObject = ' should be an object';
+const shouldBeAnArray = ' should be an Array';
+const shouldBeValid = ' is not a valid dchain property';
+const MODE = {
+ STRICT: 'strict',
+ RELAXED: 'relaxed',
+ OFF: 'off'
+};
+const MODES = []; // an array of modes
+_each(MODE, mode => MODES.push(mode));
+
+export function checkDchainSyntax(bid, mode) {
+ let dchainObj = deepClone(bid.meta.dchain);
+ let failPrefix = 'Detected something wrong in bid.meta.dchain object for bid:';
+ let failMsg = '';
+ const dchainPropList = ['ver', 'complete', 'nodes', 'ext'];
+
+ function appendFailMsg(msg) {
+ failMsg += '\n' + msg;
+ }
+
+ function printFailMsg() {
+ if (mode === MODE.STRICT) {
+ logError(failPrefix, bid, '\n', dchainObj, failMsg);
+ } else {
+ logWarn(failPrefix, bid, `\n`, dchainObj, failMsg);
+ }
+ }
+
+ let dchainProps = Object.keys(dchainObj);
+ dchainProps.forEach(prop => {
+ if (!includes(dchainPropList, prop)) {
+ appendFailMsg(`dchain.${prop}` + shouldBeValid);
+ }
+ });
+
+ if (dchainObj.complete !== 0 && dchainObj.complete !== 1) {
+ appendFailMsg(`dchain.complete should be 0 or 1`);
+ }
+
+ if (!isStr(dchainObj.ver)) {
+ appendFailMsg(`dchain.ver` + shouldBeAString);
+ }
+
+ if (hasOwn(dchainObj, 'ext')) {
+ if (!isPlainObject(dchainObj.ext)) {
+ appendFailMsg(`dchain.ext` + shouldBeAnObject);
+ }
+ }
+
+ if (!isArray(dchainObj.nodes)) {
+ appendFailMsg(`dchain.nodes` + shouldBeAnArray);
+ printFailMsg();
+ if (mode === MODE.STRICT) return false;
+ } else {
+ const nodesPropList = ['asi', 'bsid', 'rid', 'name', 'domain', 'ext'];
+ dchainObj.nodes.forEach((node, index) => {
+ if (!isPlainObject(node)) {
+ appendFailMsg(`dchain.nodes[${index}]` + shouldBeAnObject);
+ } else {
+ let nodeProps = Object.keys(node);
+ nodeProps.forEach(prop => {
+ if (!includes(nodesPropList, prop)) {
+ appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeValid);
+ }
+
+ if (prop === 'ext') {
+ if (!isPlainObject(node.ext)) {
+ appendFailMsg(`dchain.nodes[${index}].ext` + shouldBeAnObject);
+ }
+ } else {
+ if (!isStr(node[prop])) {
+ appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeAString);
+ }
+ }
+ });
+ }
+ });
+ }
+
+ if (failMsg.length > 0) {
+ printFailMsg();
+ if (mode === MODE.STRICT) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function isValidDchain(bid) {
+ let mode = MODE.STRICT;
+ const dchainConfig = config.getConfig('dchain');
+
+ if (dchainConfig && isStr(dchainConfig.validation) && MODES.indexOf(dchainConfig.validation) != -1) {
+ mode = dchainConfig.validation;
+ }
+
+ if (mode === MODE.OFF) {
+ return true;
+ } else {
+ return checkDchainSyntax(bid, mode);
+ }
+}
+
+export function addBidResponseHook(fn, adUnitCode, bid) {
+ const basicDchain = {
+ ver: '1.0',
+ complete: 0,
+ nodes: []
+ };
+
+ if (deepAccess(bid, 'meta.networkId') && deepAccess(bid, 'meta.networkName')) {
+ basicDchain.nodes.push({ name: bid.meta.networkName, bsid: bid.meta.networkId.toString() });
+ }
+ basicDchain.nodes.push({ name: bid.bidderCode });
+
+ let bidDchain = deepAccess(bid, 'meta.dchain');
+ if (bidDchain && isPlainObject(bidDchain)) {
+ let result = isValidDchain(bid);
+
+ if (result) {
+ // extra check in-case mode is OFF and there is a setup issue
+ if (isArray(bidDchain.nodes)) {
+ bid.meta.dchain.nodes.push({ asi: bid.bidderCode });
+ } else {
+ logWarn('bid.meta.dchain.nodes did not exist or was not an array; did not append prebid dchain.', bid);
+ }
+ } else {
+ // remove invalid dchain
+ delete bid.meta.dchain;
+ }
+ } else {
+ bid.meta.dchain = basicDchain;
+ }
+
+ fn(adUnitCode, bid);
+}
+
+export function init() {
+ getHook('addBidResponse').before(addBidResponseHook, 35);
+}
+
+init();
diff --git a/modules/dchain.md b/modules/dchain.md
new file mode 100644
index 00000000000..f01b3483f3c
--- /dev/null
+++ b/modules/dchain.md
@@ -0,0 +1,45 @@
+# dchain module
+
+Refer:
+- https://iabtechlab.com/buyers-json-demand-chain/
+
+## Sample code for dchain setConfig and dchain object
+```
+pbjs.setConfig({
+ "dchain": {
+ "validation": "strict"
+ }
+});
+```
+
+```
+bid.meta.dchain: {
+ "complete": 0,
+ "ver": "1.0",
+ "ext": {...},
+ "nodes": [{
+ "asi": "abc",
+ "bsid": "123",
+ "rid": "d4e5f6",
+ "name": "xyz",
+ "domain": "mno",
+ "ext": {...}
+ }, ...]
+}
+```
+
+## Workflow
+The dchain module is not enabled by default as it may not be necessary for all publishers.
+If required, dchain module can be included as following
+```
+ $ gulp build --modules=dchain,pubmaticBidAdapter,openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter
+```
+
+The dchain module will validate a bidder's dchain object (if it was defined). Bidders should assign their dchain object into `bid.meta` field. If the dchain object is valid, it will remain in the bid object for later use.
+
+If it was not defined, the dchain will create a default dchain object for prebid.
+
+## Validation modes
+- ```strict```: It is the default validation mode. In this mode, dchain object will not be accpeted from adapters if it is invalid. Errors are thrown for invalid dchain object.
+- ```relaxed```: In this mode, errors are thrown for an invalid dchain object but the invalid dchain object is still accepted from adapters.
+- ```off```: In this mode, no validations are performed and dchain object is accepted as is from adapters.
\ No newline at end of file
diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js
index f2314454ab9..94167b92bb0 100644
--- a/modules/deepintentBidAdapter.js
+++ b/modules/deepintentBidAdapter.js
@@ -1,13 +1,38 @@
-import { generateUUID, deepSetValue, deepAccess, isArray } from '../src/utils.js';
+import { generateUUID, deepSetValue, deepAccess, isArray, isInteger, logError, logWarn } from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER} from '../src/mediaTypes.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
const BIDDER_CODE = 'deepintent';
const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid';
const USER_SYNC_URL = 'https://cdn.deepintent.com/syncpixel.html';
const DI_M_V = '1.0.0';
+export const ORTB_VIDEO_PARAMS = {
+ 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'),
+ 'minduration': (value) => isInteger(value),
+ 'maxduration': (value) => isInteger(value),
+ 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10),
+ 'w': (value) => isInteger(value),
+ 'h': (value) => isInteger(value),
+ 'startdelay': (value) => isInteger(value),
+ 'placement': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5),
+ 'linearity': (value) => [1, 2].indexOf(value) !== -1,
+ 'skip': (value) => [0, 1].indexOf(value) !== -1,
+ 'skipmin': (value) => isInteger(value),
+ 'skipafter': (value) => isInteger(value),
+ 'sequence': (value) => isInteger(value),
+ 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17),
+ 'maxextended': (value) => isInteger(value),
+ 'minbitrate': (value) => isInteger(value),
+ 'maxbitrate': (value) => isInteger(value),
+ 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1,
+ 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6),
+ 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1,
+ 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1,
+ 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1,
+ 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6)
+};
export const spec = {
code: BIDDER_CODE,
- supportedMediaTypes: [BANNER],
+ supportedMediaTypes: [BANNER, VIDEO],
aliases: [],
// tagId is mandatory param
@@ -15,16 +40,38 @@ export const spec = {
let valid = false;
if (bid && bid.params && bid.params.tagId) {
if (typeof bid.params.tagId === 'string' || bid.params.tagId instanceof String) {
- valid = true;
+ if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) {
+ if (bid.mediaTypes[VIDEO].hasOwnProperty('context')) {
+ valid = true;
+ }
+ } else {
+ valid = true;
+ }
}
}
return valid;
},
- interpretResponse: function(bidResponse, request) {
+ interpretResponse: function(bidResponse, bidRequest) {
let responses = [];
if (bidResponse && bidResponse.body) {
- let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : [];
- responses = bids.map(bid => formatResponse(bid))
+ try {
+ let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : [];
+ if (bids) {
+ bids.forEach(bidObj => {
+ let newBid = formatResponse(bidObj);
+ let mediaType = _checkMediaType(bidObj);
+ if (mediaType === BANNER) {
+ newBid.mediaType = BANNER;
+ } else if (mediaType === VIDEO) {
+ newBid.mediaType = VIDEO;
+ newBid.vastXml = bidObj.adm;
+ }
+ responses.push(newBid);
+ });
+ }
+ } catch (err) {
+ logError(err);
+ }
}
return responses;
},
@@ -73,6 +120,17 @@ export const spec = {
}
};
+function _checkMediaType(bid) {
+ let videoRegex = new RegExp(/VAST\s+version/);
+ let mediaType;
+ if (bid.adm && bid.adm.indexOf('deepintent_wrapper') >= 0) {
+ mediaType = BANNER;
+ } else if (videoRegex.test(bid.adm)) {
+ mediaType = VIDEO;
+ }
+ return mediaType;
+}
+
function clean(obj) {
for (let propName in obj) {
if (obj[propName] === null || obj[propName] === undefined) {
@@ -100,16 +158,55 @@ function formatResponse(bid) {
}
function buildImpression(bid) {
- return {
+ let impression = {};
+ impression = {
id: bid.bidId,
tagid: bid.params.tagId || '',
- secure: window.location.protocol === 'https' ? 1 : 0,
- banner: buildBanner(bid),
+ secure: window.location.protocol === 'https:' ? 1 : 0,
displaymanager: 'di_prebid',
displaymanagerver: DI_M_V,
ext: buildCustomParams(bid)
};
+ if (deepAccess(bid, 'mediaTypes.banner')) {
+ impression['banner'] = buildBanner(bid);
+ }
+ if (deepAccess(bid, 'mediaTypes.video')) {
+ impression['video'] = _buildVideo(bid);
+ }
+ return impression;
}
+
+function _buildVideo(bid) {
+ const videoObj = {};
+ const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {});
+ const videoBidderParams = deepAccess(bid, 'params.video', {});
+ const computedParams = {};
+
+ if (Array.isArray(videoAdUnitParams.playerSize)) {
+ const tempSize = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize;
+ computedParams.w = tempSize[0];
+ computedParams.h = tempSize[1];
+ }
+
+ const videoParams = {
+ ...computedParams,
+ ...videoAdUnitParams,
+ ...videoBidderParams
+ };
+
+ Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => {
+ if (videoParams.hasOwnProperty(paramName)) {
+ if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) {
+ videoObj[paramName] = videoParams[paramName];
+ } else {
+ logWarn(`The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`);
+ }
+ }
+ });
+
+ return videoObj;
+};
+
function buildCustomParams(bid) {
if (bid.params && bid.params.custom) {
return {
diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md
index 79a6a1679e2..84c375d69a4 100644
--- a/modules/deepintentBidAdapter.md
+++ b/modules/deepintentBidAdapter.md
@@ -8,7 +8,7 @@ Maintainer: prebid@deepintent.com
# Description
-Deepintent currently supports the BANNER type ads through prebid js
+Deepintent currently supports the BANNER and VIDEO type ads through prebid js
Module that connects to Deepintent's demand sources.
@@ -40,6 +40,41 @@ Module that connects to Deepintent's demand sources.
];
```
+# Sample Video Ad Unit
+```
+var adVideoAdUnits = [
+{
+ code: 'test-div-video',
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480], // required
+ context: 'instream' //required
+ }
+ },
+ bids: [{
+ bidder: 'deepintent',
+ params: {
+ tagId: '1300', // Required parameter // required
+ video: {
+ mimes: ['video/mp4','video/x-flv'], // required
+ skippable: true, // optional
+ minduration: 5, // optional
+ maxduration: 30, // optional
+ startdelay: 5, // optional
+ playbackmethod: [1,3], // optional
+ api: [ 1, 2 ], // optional
+ protocols: [ 2, 3 ], // optional
+ battr: [ 13, 14 ], // optional
+ linearity: 1, // optional
+ placement: 2, // optional
+ minbitrate: 10, // optional
+ maxbitrate: 10 // optional
+ }
+ }
+ }]
+}]
+```
+
###Recommended User Sync Configuration
```javascript
diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js
new file mode 100644
index 00000000000..33df5bd252e
--- /dev/null
+++ b/modules/deltaprojectsBidAdapter.js
@@ -0,0 +1,252 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
+import {
+ _each, _map, isFn, isNumber, createTrackPixelHtml, deepAccess, parseUrl, logWarn, logError
+} from '../src/utils.js';
+import {config} from '../src/config.js';
+
+export const BIDDER_CODE = 'deltaprojects';
+export const BIDDER_ENDPOINT_URL = 'https://d5p.de17a.com/dogfight/prebid';
+export const USERSYNC_URL = 'https://userservice.de17a.com/getuid/prebid';
+
+/** -- isBidRequestValid --**/
+function isBidRequestValid(bid) {
+ if (!bid) return false;
+
+ if (bid.bidder !== BIDDER_CODE) return false;
+
+ // publisher id is required
+ const publisherId = deepAccess(bid, 'params.publisherId')
+ if (!publisherId) {
+ logError('Invalid bid request, missing publisher id in params');
+ return false;
+ }
+
+ return true;
+}
+
+/** -- Build requests --**/
+function buildRequests(validBidRequests, bidderRequest) {
+ /** == shared ==**/
+ // -- build id
+ const id = bidderRequest.auctionId;
+
+ // -- build site
+ const loc = parseUrl(bidderRequest.refererInfo.referer);
+ const publisherId = setOnAny(validBidRequests, 'params.publisherId');
+ const siteId = setOnAny(validBidRequests, 'params.siteId');
+ const site = {
+ id: siteId,
+ domain: loc.hostname,
+ page: loc.href,
+ ref: loc.href,
+ publisher: { id: publisherId },
+ };
+
+ // -- build device
+ const ua = navigator.userAgent;
+ const device = {
+ ua,
+ w: screen.width,
+ h: screen.height
+ }
+
+ // -- build user, reg
+ let user = { ext: {} };
+ const regs = { ext: {} };
+ const gdprConsent = bidderRequest && bidderRequest.gdprConsent;
+ if (gdprConsent) {
+ user.ext = { consent: gdprConsent.consentString };
+ if (typeof gdprConsent.gdprApplies == 'boolean') {
+ regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0
+ }
+ }
+
+ // -- build tmax
+ let tmax = (bidderRequest && bidderRequest.timeout > 0) ? bidderRequest.timeout : undefined;
+
+ // build bid specific
+ return validBidRequests.map(validBidRequest => {
+ const openRTBRequest = buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs);
+ return {
+ method: 'POST',
+ url: BIDDER_ENDPOINT_URL,
+ data: openRTBRequest,
+ options: { contentType: 'application/json' },
+ bids: [validBidRequest],
+ };
+ });
+}
+
+function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs) {
+ // build cur
+ const currency = config.getConfig('currency.adServerCurrency') || deepAccess(validBidRequest, 'params.currency');
+ const cur = currency && [currency];
+
+ // build impression
+ const impression = buildImpression(validBidRequest, currency);
+
+ // build test
+ const test = deepAccess(validBidRequest, 'params.test') ? 1 : 0
+
+ const at = 1
+
+ // build source
+ const source = {
+ tid: validBidRequest.transactionId,
+ fd: 1,
+ }
+
+ return {
+ id,
+ at,
+ imp: [impression],
+ site,
+ device,
+ user,
+ test,
+ tmax,
+ cur,
+ source,
+ regs,
+ ext: {},
+ };
+}
+
+function buildImpression(bid, currency) {
+ const impression = {
+ id: bid.bidId,
+ tagid: bid.params.tagId,
+ ext: {},
+ };
+
+ const bannerMediaType = deepAccess(bid, `mediaTypes.${BANNER}`);
+ impression.banner = buildImpressionBanner(bid, bannerMediaType);
+
+ // bid floor
+ const bidFloor = getBidFloor(bid, BANNER, '*', currency);
+ if (bidFloor) {
+ impression.bidfloor = bidFloor.floor;
+ impression.bidfloorcur = bidFloor.currency;
+ }
+
+ return impression;
+}
+
+function buildImpressionBanner(bid, bannerMediaType) {
+ const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes;
+ return {
+ format: _map(bannerSizes, ([width, height]) => ({ w: width, h: height })),
+ };
+}
+
+/** -- Interpret response --**/
+function interpretResponse(serverResponse) {
+ if (!serverResponse.body) {
+ logWarn('Response body is invalid, return !!');
+ return [];
+ }
+
+ const { body: { id, seatbid, cur } } = serverResponse;
+ if (!id || !seatbid) {
+ logWarn('Id / seatbid of response is invalid, return !!');
+ return [];
+ }
+
+ const bidResponses = [];
+
+ _each(seatbid, seatbid => {
+ _each(seatbid.bid, bid => {
+ const bidObj = {
+ requestId: bid.impid,
+ cpm: parseFloat(bid.price),
+ width: parseInt(bid.w),
+ height: parseInt(bid.h),
+ creativeId: bid.crid || bid.id,
+ dealId: bid.dealid || null,
+ currency: cur,
+ netRevenue: true,
+ ttl: 60,
+ };
+
+ bidObj.mediaType = BANNER;
+ bidObj.ad = bid.adm;
+ if (bid.nurl) {
+ bidObj.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl));
+ }
+ if (bid.ext) {
+ bidObj[BIDDER_CODE] = bid.ext;
+ }
+ bidResponses.push(bidObj);
+ });
+ });
+ return bidResponses;
+}
+
+/** -- On Bid Won -- **/
+function onBidWon(bid) {
+ let cpm = bid.cpm;
+ if (bid.currency && bid.currency !== bid.originalCurrency && typeof bid.getCpmInNewCurrency === 'function') {
+ cpm = bid.getCpmInNewCurrency(bid.originalCurrency);
+ }
+ const wonPrice = Math.round(cpm * 1000000);
+ const wonPriceMacroPatten = /\$\{AUCTION_PRICE:B64\}/g;
+ bid.ad = bid.ad.replace(wonPriceMacroPatten, wonPrice);
+}
+
+/** -- Get user syncs --**/
+function getUserSyncs(syncOptions, serverResponses, gdprConsent) {
+ const syncs = []
+
+ if (syncOptions.pixelEnabled) {
+ let gdprParams;
+ if (gdprConsent) {
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
+ } else {
+ gdprParams = `?gdpr_consent=${gdprConsent.consentString}`;
+ }
+ } else {
+ gdprParams = '';
+ }
+ syncs.push({
+ type: 'image',
+ url: USERSYNC_URL + gdprParams
+ });
+ }
+ return syncs;
+}
+
+/** -- Get bid floor --**/
+export function getBidFloor(bid, mediaType, size, currency) {
+ if (isFn(bid.getFloor)) {
+ const bidFloorCurrency = currency || 'USD';
+ const bidFloor = bid.getFloor({currency: bidFloorCurrency, mediaType: mediaType, size: size});
+ if (isNumber(bidFloor.floor)) {
+ return bidFloor;
+ }
+ }
+}
+
+/** -- Helper methods --**/
+function setOnAny(collection, key) {
+ for (let i = 0, result; i < collection.length; i++) {
+ result = deepAccess(collection[i], key);
+ if (result) {
+ return result;
+ }
+ }
+}
+
+/** -- Register -- */
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER],
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ onBidWon,
+ getUserSyncs,
+};
+
+registerBidder(spec);
diff --git a/modules/deltaprojectsBidAdapter.md b/modules/deltaprojectsBidAdapter.md
new file mode 100644
index 00000000000..97cef4dd228
--- /dev/null
+++ b/modules/deltaprojectsBidAdapter.md
@@ -0,0 +1,32 @@
+# Overview
+
+```
+Module Name: Delta Projects Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: dev@deltaprojects.com
+```
+
+# Description
+
+Connects to Delta Projects DSP for bids.
+
+# Test Parameters
+```
+// define banner unit
+var bannerUnit = {
+ code: 'div-gpt-ad-1460505748561-0',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300,600]],
+ }
+ },
+ // Replace this object to test a new Adapter!
+ bids: [{
+ bidder: 'deltaprojects',
+ params: {
+ publisherId: '4' //required
+ }
+ }]
+};
+```
+
diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js
index a086091d464..26a8257077a 100644
--- a/modules/dgkeywordRtdProvider.js
+++ b/modules/dgkeywordRtdProvider.js
@@ -78,7 +78,6 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us
}
}, null, {
withCredentials: true,
- contentType: 'application/json',
});
setTimeout(function () {
if (!isFinish) {
diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js
new file mode 100644
index 00000000000..55a2f4a8604
--- /dev/null
+++ b/modules/displayioBidAdapter.js
@@ -0,0 +1,157 @@
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+
+const BIDDER_VERSION = '1.0.0';
+const BIDDER_CODE = 'displayio';
+const GVLID = 999;
+const BID_TTL = 300;
+const SUPPORTED_AD_TYPES = [BANNER, VIDEO];
+const DEFAULT_CURRENCY = 'USD';
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: GVLID,
+ supportedMediaTypes: SUPPORTED_AD_TYPES,
+ isBidRequestValid: function(bid) {
+ return !!(bid.params && bid.params.placementId && bid.params.siteId &&
+ bid.params.adsSrvDomain && bid.params.cdnDomain);
+ },
+ buildRequests: function (bidRequests, bidderRequest) {
+ return bidRequests.map(bid => {
+ let url = '//' + bid.params.adsSrvDomain + '/srv?method=getPlacement&app=' +
+ bid.params.siteId + '&placement=' + bid.params.placementId;
+ const data = this._getPayload(bid, bidderRequest);
+ return {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json;charset=utf-8'},
+ url,
+ data
+ };
+ });
+ },
+ interpretResponse: function (serverResponse, serverRequest) {
+ const ads = serverResponse.body.data.ads;
+ const bidResponses = [];
+ const { data } = serverRequest.data;
+ if (ads.length) {
+ const adData = ads[0].ad.data;
+ const bidResponse = {
+ requestId: data.id,
+ cpm: adData.ecpm,
+ width: adData.w,
+ height: adData.h,
+ netRevenue: true,
+ ttl: BID_TTL,
+ creativeId: adData.adId || 0,
+ currency: DEFAULT_CURRENCY,
+ referrer: data.data.ref,
+ mediaType: ads[0].ad.subtype,
+ ad: adData.markup,
+ placement: data.placement,
+ adData: adData
+ };
+ if (bidResponse.vastUrl === 'videoVast') {
+ bidResponse.vastUrl = adData.videos[0].url
+ }
+ bidResponses.push(bidResponse);
+ }
+ return bidResponses;
+ },
+ _getPayload: function (bid, bidderRequest) {
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
+ const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => {
+ let r = Math.random() * 16 | 0;
+ let v = c === 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+ const { params } = bid;
+ const { siteId, placementId } = params;
+ const { refererInfo, uspConsent, gdprConsent } = bidderRequest;
+ const mediation = {consent: '-1', gdpr: '-1'};
+ if (gdprConsent) {
+ if (gdprConsent.consentString !== undefined) {
+ mediation.consent = gdprConsent.consentString;
+ }
+ if (gdprConsent.gdprApplies !== undefined) {
+ mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0';
+ }
+ }
+ const payload = {
+ userSession,
+ data: {
+ id: bid.bidId,
+ action: 'getPlacement',
+ app: siteId,
+ placement: placementId,
+ data: {
+ pagecat: params.pageCategory ? params.pageCategory.split(',').map(k => k.trim()) : [],
+ keywords: params.keywords ? params.keywords.split(',').map(k => k.trim()) : [],
+ lang_content: document.documentElement.lang,
+ lang: window.navigator.language,
+ domain: window.location.hostname,
+ page: window.location.href,
+ ref: refererInfo.referer,
+ userids: _getUserIDs(),
+ geo: '',
+ },
+ complianceData: {
+ child: '-1',
+ us_privacy: uspConsent,
+ dnt: window.navigator.doNotTrack,
+ iabConsent: {},
+ mediation: {
+ consent: mediation.consent,
+ gdpr: mediation.gdpr,
+ }
+ },
+ integration: 'JS',
+ omidpn: 'Displayio',
+ mediationPlatform: 0,
+ prebidVersion: BIDDER_VERSION,
+ device: {
+ w: window.screen.width,
+ h: window.screen.height,
+ connection_type: connection ? connection.effectiveType : '',
+ }
+ }
+ }
+ if (navigator.permissions) {
+ navigator.permissions.query({ name: 'geolocation' })
+ .then((result) => {
+ if (result.state === 'granted') {
+ payload.data.data.geo = _getGeoData();
+ }
+ });
+ }
+ return payload
+ }
+};
+
+function _getUserIDs () {
+ let ids = {};
+ try {
+ ids = window.owpbjs.getUserIdsAsEids();
+ } catch (e) {}
+ return ids;
+}
+
+async function _getGeoData () {
+ let geoData = null;
+ const getCurrentPosition = () => {
+ return new Promise((resolve, reject) =>
+ navigator.geolocation.getCurrentPosition(resolve, reject)
+ );
+ }
+ try {
+ const position = await getCurrentPosition();
+ let {latitude, longitude, accuracy} = position.coords;
+ geoData = {
+ 'lat': latitude,
+ 'lng': longitude,
+ 'precision': accuracy
+ };
+ } catch (e) {}
+ return geoData
+}
+
+registerBidder(spec);
diff --git a/modules/displayioBidAdapter.md b/modules/displayioBidAdapter.md
new file mode 100644
index 00000000000..41505ee966e
--- /dev/null
+++ b/modules/displayioBidAdapter.md
@@ -0,0 +1,148 @@
+# Overview
+
+```
+Module Name: DisplayIO Bidder Adapter
+Module Type: Bidder Adapter
+```
+
+# Description
+
+Module that connects to display.io's demand sources.
+Web mobile (not relevant for web desktop).
+
+
+#Features
+| Feature | | Feature | |
+|---------------|---------------------------------------------------------|-----------------------|-----|
+| Bidder Code | displayio | Prebid member | no |
+| Media Types | Banner, video.
Sizes (display 320x480 / vertical video) | GVL ID | no |
+| GDPR Support | yes | Prebid.js Adapter | yes |
+| USP Support | yes | Prebid Server Adapter | no |
+
+
+#Global configuration
+```javascript
+
+
+
+
+
+
+
+
+
+
+ Basic Prebid.js Example
+ Div-1
+
+
+
+
+
+
+ Div-2
+
+
+
+
+
+
+