diff --git a/.babelrc.js b/.babelrc.js
index 52fd762007e..add243a5b5d 100644
--- a/.babelrc.js
+++ b/.babelrc.js
@@ -1,26 +1 @@
-
-let path = require('path');
-
-function useLocal(module) {
- return require.resolve(module, {
- paths: [
- __dirname
- ]
- })
-}
-
-module.exports = {
- "presets": [
- [
- useLocal('@babel/preset-env'),
- {
- "useBuiltIns": "entry",
- "corejs": "3.13.0"
- }
- ]
- ],
- "plugins": [
- path.resolve(__dirname, './plugins/pbjsGlobals.js'),
- useLocal('babel-plugin-transform-object-assign')
- ]
-};
+module.exports = require('./babelConfig.js')();
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 803140dbe0b..c11f87b6f59 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,7 +7,7 @@ aliases:
- &environment
docker:
# specify the version you desire here
- - image: circleci/node:12.16.1
+ - image: circleci/node:12.16.1-browsers
resource_class: xlarge
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
@@ -16,71 +16,72 @@ aliases:
working_directory: ~/Prebid.js
- &restore_dep_cache
- keys:
- - v1-dependencies-{{ checksum "package.json" }}
- # fallback to using the latest cache if no exact match is found
- - v1-dependencies-
+ keys:
+ - v1-dependencies-{{ checksum "package.json" }}
+ # fallback to using the latest cache if no exact match is found
+ - v1-dependencies-
- &save_dep_cache
- paths:
- - node_modules
- key: v1-dependencies-{{ checksum "package.json" }}
+ paths:
+ - node_modules
+ key: v1-dependencies-{{ checksum "package.json" }}
- &install
- name: Install gulp cli
- command: sudo npm install -g gulp-cli
+ name: Install gulp cli
+ command: sudo npm install -g gulp-cli
-version: 2.1
-orbs:
- aws-s3: circleci/aws-s3@2.0.0
+ - &run_unit_test
+ name: BrowserStack testing
+ command: gulp test --browserstack --nolintfix
+
+ - &run_endtoend_test
+ name: BrowserStack End to end testing
+ command: gulp e2e-test
+
+ # Download and run BrowserStack local
+ - &setup_browserstack
+ name : Download BrowserStack Local binary and start it.
+ command : |
+ # Download the browserstack binary file
+ wget "https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip"
+ # Unzip it
+ unzip BrowserStackLocal-linux-x64.zip
+ # Run the file with user's access key
+ ./BrowserStackLocal ${BROWSERSTACK_ACCESS_KEY} &
+
+ - &unit_test_steps
+ - checkout
+ - restore_cache: *restore_dep_cache
+ - run: npm ci
+ - save_cache: *save_dep_cache
+ - run: *install
+ - run: *setup_browserstack
+ - run: *run_unit_test
+
+ - &endtoend_test_steps
+ - checkout
+ - restore_cache: *restore_dep_cache
+ - run: npm install
+ - save_cache: *save_dep_cache
+ - run: *install
+ - run: *setup_browserstack
+ - run: *run_endtoend_test
+
+version: 2
jobs:
build:
<<: *environment
- steps:
- - checkout
- - run: echo "export UPLOAD_DEST='s3://embedproduction/files/instbid-$(git describe --tags).js'" >> "$BASH_ENV"
- - restore_cache: *restore_dep_cache
- - run: npm install
- - save_cache: *save_dep_cache
- - run: *install
- - run:
- name: Build Prebid.js
- command: gulp build --modules=modules.json
- - aws-s3/copy:
- from: build/dist/prebid.js
- to: '"$UPLOAD_DEST"'
- arguments: --cache-control 'max-age=86400'
- build_v6:
- <<: *environment
- steps:
- - checkout
- - run: echo "export UPLOAD_DEST='s3://embedproduction/files/instbid-$(git describe --tags).js'" >> "$BASH_ENV"
- - restore_cache: *restore_dep_cache
- - run: npm install
- - save_cache: *save_dep_cache
- - run: *install
- - run:
- name: Build Prebid.js version 6
- command: gulp build --modules=modules.json
- - aws-s3/copy:
- from: build/dist/prebid.js
- to: '"$UPLOAD_DEST"'
- arguments: --cache-control 'max-age=86400'
+ steps: *unit_test_steps
+ e2etest:
+ <<: *environment
+ steps: *endtoend_test_steps
workflows:
version: 2
- build:
+ commit:
jobs:
- - build:
- context: org-aws
- filters:
- branches:
- only:
- - master
- - build_v6:
- context: org-aws
- filters:
- branches:
- only:
- - prebid6
+ - build
+
+experimental:
+ pipelines: true
diff --git a/.eslintrc.js b/.eslintrc.js
index d3379d70919..95515e8ba98 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -45,12 +45,19 @@ module.exports = {
'no-throw-literal': 'off',
'no-undef': 2,
'no-useless-escape': 'off',
- 'no-console': 'error'
+ 'no-console': 'error',
},
overrides: Object.keys(allowedModules).map((key) => ({
files: key + '/**/*.js',
rules: {
- 'prebid/validate-imports': ['error', allowedModules[key]]
+ 'prebid/validate-imports': ['error', allowedModules[key]],
+ 'no-restricted-globals': [
+ 'error',
+ {
+ name: 'require',
+ message: 'use import instead'
+ }
+ ]
}
})).concat([{
// code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain.
diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml
new file mode 100644
index 00000000000..4397337b4c7
--- /dev/null
+++ b/.github/workflows/issue_tracker.yml
@@ -0,0 +1,89 @@
+name: Issue tracking
+on:
+ issues:
+ types:
+ - opened
+jobs:
+ track_issue:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Generate token
+ id: generate_token
+ uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
+ with:
+ app_id: ${{ secrets.ISSUE_APP_ID }}
+ private_key: ${{ secrets.ISSUE_APP_PEM }}
+
+ - name: Get project data
+ env:
+ GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+ ORGANIZATION: prebid
+ DATE_FIELD: Created on
+ PROJECT_NUMBER: 2
+ run: |
+ gh api graphql -f query='
+ query($org: String!, $number: Int!) {
+ organization(login: $org){
+ projectNext(number: $number) {
+ id
+ fields(first:100) {
+ nodes {
+ id
+ name
+ settings
+ }
+ }
+ }
+ }
+ }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
+
+ echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
+ echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV
+
+ - name: Add issue to project
+ env:
+ GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+ ISSUE_ID: ${{ github.event.issue.node_id }}
+ run: |
+ gh api graphql -f query='
+ mutation($project:ID!, $issue:ID!) {
+ addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
+ projectNextItem {
+ id,
+ content {
+ ... on Issue {
+ createdAt
+ }
+ ... on PullRequest {
+ createdAt
+ }
+ }
+ }
+ }
+ }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json
+
+ echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV
+ echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV
+
+ - name: Set fields
+ env:
+ GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+ run: |
+ gh api graphql -f query='
+ mutation (
+ $project: ID!
+ $item: ID!
+ $date_field: ID!
+ $date_value: String!
+ ) {
+ set_creation_date: updateProjectNextItemField(input: {
+ projectId: $project
+ itemId: $item
+ fieldId: $date_field
+ value: $date_value
+ }) {
+ projectNextItem {
+ id
+ }
+ }
+ }' -f project=$PROJECT_ID -f item=$ITEM_ID -f date_field=$DATE_FIELD_ID -f date_value=$ITEM_CREATION_DATE --silent
diff --git a/PR_REVIEW.md b/PR_REVIEW.md
index 1152e2942bf..2934a30fb47 100644
--- a/PR_REVIEW.md
+++ b/PR_REVIEW.md
@@ -51,11 +51,16 @@ Follow steps above for general review process. In addition, please verify the fo
- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed.
- All bidder parameter conventions must be followed:
- Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit.
- - First party data must be read from [`fpd.context` and `fpd.user`](https://docs.prebid.org/dev-docs/publisher-api-reference.html#setConfig-fpd).
+ - First party data must be read from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd).
- Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function.
- Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain.
- - The bidRequest page referrer must checked in addition to any bidder-specific parameter.
+ - The bidderRequest.refererInfo.referer must be checked in addition to any bidder-specific parameter.
- If they're getting the COPPA flag, it must come from config.getConfig('coppa');
+ - Page position must come from bidrequest.mediaTypes.banner.pos or bidrequest.mediaTypes.video.pos
+ - Global OpenRTB fields should come from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd):
+ - bcat, battr, badv
+ - Impression-specific OpenRTB fields should come from bidrequest.ortb2imp
+ - instl
- Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file):
- If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true`
- If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true`
diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md
index bfbd0772c3e..b68495ed4ae 100644
--- a/RELEASE_SCHEDULE.md
+++ b/RELEASE_SCHEDULE.md
@@ -3,12 +3,7 @@
- [Release Process](#release-process)
- [1. Make sure that all PRs have been named and labeled properly per the PR Process](#1-make-sure-that-all-prs-have-been-named-and-labeled-properly-per-the-pr-process)
- [2. Make sure all browserstack tests are passing](#2-make-sure-all-browserstack-tests-are-passing)
- - [3. Prepare Prebid Code](#3-prepare-prebid-code)
- - [4. Verify the Release](#4-verify-the-release)
- - [5. Create a GitHub release](#5-create-a-github-release)
- - [6. Update coveralls _(skip for legacy)_](#6-update-coveralls-skip-for-legacy)
- - [7. Distribute the code](#7-distribute-the-code)
- - [8. Increment Version for Next Release](#8-increment-version-for-next-release)
+ - [3. Start the release](#3-start-the-release)
- [Beta Releases](#beta-releases)
- [FAQs](#faqs)
@@ -21,12 +16,10 @@ it will be about a week before the Prebid Org [Download Page](http://prebid.org/
You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases)
-Announcements regarding releases will be made to the #headerbidding-dev channel in subredditadops.slack.com.
+Announcements regarding releases will be made to the #prebid-js channel in prebid.slack.com.
## Release Process
-_Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for your repo, all of the following git commands will have to be modified to reference the proper remote (e.g. `upstream`)_
-
### 1. Make sure that all PRs have been named and labeled properly per the [PR Process](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md#general-pr-review-process)
* Do this by checking the latest draft release from the [releases page](https://github.com/prebid/Prebid.js/releases) and make sure nothing appears in the first section called "In This Release". If they do, please open the PRs and add the appropriate labels.
* Do a quick check that all the titles/descriptions look ok, and if not, adjust the PR title.
@@ -57,61 +50,10 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for
```
-### 3. Prepare Prebid Code
-
- Update the package.json version to become the current release. Then commit your changes.
-
- ```
- git commit -m "Prebid 4.x.x Release"
- git push
- ```
-
-### 4. Verify the Release
-
- Make sure your there are no more merges to master branch. Prebid code is clean and up to date.
-
-### 5. Create a GitHub release
-
- Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct version is set and the master branch is selected in the dropdown. Click `Publish release`. GitHub will create release tag.
-
- Pull these changes locally by running command
- ```
- git pull
- git fetch --tags
- ```
-
- and verify the tag.
-
-### 6. Update coveralls _(skip for legacy)_
-
- We use https://coveralls.io/ to show parts of code covered by unit tests.
-
- Set the environment variables. You may want to add these to your `~/.bashrc` for convenience.
- ```
- export COVERALLS_SERVICE_NAME="travis-ci"
- export COVERALLS_REPO_TOKEN="talk to Matt Kendall"
- ```
-
- Run `gulp coveralls` to update code coverage history.
-
-### 7. Distribute the code
-
- _Note: do not go to step 8 until step 7 has been verified completed._
-
- Reach out to any of the Appnexus folks to trigger the jenkins job.
-
- // TODO:
- Jenkins job is moving files to appnexus cdn, pushing prebid.js to npm, purging cache and sending notification to slack.
- Move all the files from Appnexus CDN to jsDelivr and create bash script to do above tasks.
-
-### 8. Increment Version for Next Release
-
- Update the version by manually editing Prebid's `package.json` to become "4.x.x-pre" (using the values for the next release). Then commit your changes.
- ```
- git commit -m "Increment pre version"
- git push
- ```
+### 3. Start the release
+Follow the instructions at https://github.com/prebid/prebidjs-releaser. Note that you will need to be a member of the [https://github.com/orgs/prebid/teams/prebidjs-release](prebidjs-release) GitHub team.
+
## Beta Releases
Prebid.js features may be released as Beta or as Generally Available (GA).
diff --git a/allowedModules.js b/allowedModules.js
index 81920cdc15f..be9a2dc2abf 100644
--- a/allowedModules.js
+++ b/allowedModules.js
@@ -1,12 +1,5 @@
const sharedWhiteList = [
- 'core-js-pure/features/array/find', // no ie11
- 'core-js-pure/features/array/includes', // no ie11
- 'core-js-pure/features/set', // ie11 supports Set but not Set#values
- 'core-js-pure/features/string/includes', // no ie11
- 'core-js-pure/features/number/is-integer', // no ie11,
- 'core-js-pure/features/array/from', // no ie11
- 'core-js-pure/web/url-search-params' // no ie11
];
module.exports = {
diff --git a/babelConfig.js b/babelConfig.js
new file mode 100644
index 00000000000..c1ddc11b689
--- /dev/null
+++ b/babelConfig.js
@@ -0,0 +1,30 @@
+
+let path = require('path');
+
+function useLocal(module) {
+ return require.resolve(module, {
+ paths: [
+ __dirname
+ ]
+ })
+}
+
+module.exports = function (test = false) {
+ return {
+ 'presets': [
+ [
+ useLocal('@babel/preset-env'),
+ {
+ 'useBuiltIns': 'entry',
+ 'corejs': '3.13.0',
+ // a lot of tests use sinon.stub & others that stopped working on ES6 modules with webpack 5
+ 'modules': test ? 'commonjs' : 'auto',
+ }
+ ]
+ ],
+ 'plugins': [
+ path.resolve(__dirname, './plugins/pbjsGlobals.js'),
+ useLocal('babel-plugin-transform-object-assign'),
+ ],
+ }
+}
diff --git a/gulpfile.js b/gulpfile.js
index 6ecfee1b672..ff49436384b 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -8,7 +8,6 @@ var gutil = require('gulp-util');
var connect = require('gulp-connect');
var webpack = require('webpack');
var webpackStream = require('webpack-stream');
-var terser = require('gulp-terser');
var gulpClean = require('gulp-clean');
var KarmaServer = require('karma').Server;
var karmaConfMaker = require('./karma.conf.maker.js');
@@ -33,8 +32,8 @@ var prebid = require('./package.json');
var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10);
var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '*/\n';
var port = 9999;
-const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost';
-const FAKE_SERVER_PORT = 4444;
+const INTEG_SERVER_HOST = argv.host ? argv.host : 'localhost';
+const INTEG_SERVER_PORT = 4444;
const { spawn } = require('child_process');
// these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules
@@ -117,7 +116,10 @@ viewReview.displayName = 'view-review';
function makeDevpackPkg() {
var cloned = _.cloneDeep(webpackConfig);
- cloned.devtool = 'source-map';
+ Object.assign(cloned, {
+ devtool: 'source-map',
+ mode: 'development'
+ })
var externalModules = helpers.getArgModules();
const analyticsSources = helpers.getAnalyticsSources();
@@ -132,7 +134,9 @@ function makeDevpackPkg() {
function makeWebpackPkg() {
var cloned = _.cloneDeep(webpackConfig);
- delete cloned.devtool;
+ if (!argv.sourceMaps) {
+ delete cloned.devtool;
+ }
var externalModules = helpers.getArgModules();
@@ -142,11 +146,19 @@ function makeWebpackPkg() {
return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js'))
.pipe(helpers.nameModules(externalModules))
.pipe(webpackStream(cloned, webpack))
- .pipe(terser())
- .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid })))
.pipe(gulp.dest('build/dist'));
}
+function addBanner() {
+ const sm = argv.sourceMaps;
+
+ return gulp.src(['build/dist/prebid-core.js'])
+ .pipe(gulpif(sm, sourcemaps.init({loadMaps: true})))
+ .pipe(header(banner, {prebid}))
+ .pipe(gulpif(sm, sourcemaps.write('.')))
+ .pipe(gulp.dest('build/dist'))
+}
+
function getModulesListToAddInBanner(modules) {
return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.';
}
@@ -155,9 +167,9 @@ function gulpBundle(dev) {
return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist')));
}
-function nodeBundle(modules) {
+function nodeBundle(modules, dev = false) {
return new Promise((resolve, reject) => {
- bundle(false, modules)
+ bundle(dev, modules)
.on('error', (err) => {
reject(err);
})
@@ -171,6 +183,7 @@ function nodeBundle(modules) {
function bundle(dev, moduleArr) {
var modules = moduleArr || helpers.getArgModules();
var allModules = helpers.getModuleNames(modules);
+ const sm = dev || argv.sourceMaps;
if (modules.length === 0) {
modules = allModules.filter(module => explicitModules.indexOf(module) === -1);
@@ -202,13 +215,13 @@ function bundle(dev, moduleArr) {
)
// Need to uodate the "Modules: ..." section in comment with the current modules list
.pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3')))
- .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true })))
+ .pipe(gulpif(sm, sourcemaps.init({ loadMaps: true })))
.pipe(concat(outputFileName))
.pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', {
global: prebid.globalVarName
}
)))
- .pipe(gulpif(dev, sourcemaps.write('.')));
+ .pipe(gulpif(sm, sourcemaps.write('.')));
}
// Run the unit tests.
@@ -230,41 +243,18 @@ function testTaskMaker(options = {}) {
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
- ];
- }
-
- // 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}`);
- });
-
- execa(wdioCmd, wdioOpts, { stdio: 'inherit' })
+ const integ = startIntegServer();
+ startLocalServer();
+ runWebdriver(options)
.then(stdout => {
// kill fake server
- fakeServer.kill('SIGINT');
+ integ.kill('SIGINT');
done();
process.exit(0);
})
.catch(err => {
// kill fake server
- fakeServer.kill('SIGINT');
+ integ.kill('SIGINT');
done(new Error(`Tests failed with error: ${err}`));
process.exit(1);
});
@@ -283,6 +273,26 @@ function testTaskMaker(options = {}) {
const test = testTaskMaker();
+function runWebdriver({file}) {
+ process.env.TEST_SERVER_HOST = argv.host || 'localhost';
+ let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio');
+ let wdioConf = path.join(__dirname, 'wdio.conf.js');
+ let wdioOpts;
+
+ if (file) {
+ wdioOpts = [
+ wdioConf,
+ `--spec`,
+ `${file}`
+ ]
+ } else {
+ wdioOpts = [
+ wdioConf
+ ];
+ }
+ return execa(wdioCmd, wdioOpts, { stdio: 'inherit' });
+}
+
function newKarmaCallback(done) {
return function (exitCode) {
if (exitCode) {
@@ -322,41 +332,29 @@ function buildPostbid() {
.pipe(gulp.dest('build/postbid/'));
}
-function setupE2e(done) {
- if (!argv.host) {
- throw new gutil.PluginError({
- plugin: 'E2E test',
- message: gutil.colors.red('Host should be defined e.g. ap.localhost, anlocalhost. localhost cannot be used as safari browserstack is not able to connect to localhost')
- });
+function startIntegServer(dev = false) {
+ const args = ['./test/fake-server/index.js', `--port=${INTEG_SERVER_PORT}`, `--host=${INTEG_SERVER_HOST}`];
+ if (dev) {
+ args.push('--dev=true')
}
- process.env.TEST_SERVER_HOST = argv.host;
- if (argv.https) {
- process.env.TEST_SERVER_PROTOCOL = argv.https;
- }
- argv.e2e = true;
- done();
-}
-
-function injectFakeServerEndpoint() {
- return gulp.src(['build/dist/*.js'])
- .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`))
- .pipe(gulp.dest('build/dist'));
-}
-
-function injectFakeServerEndpointDev() {
- return gulp.src(['build/dev/*.js'])
- .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`))
- .pipe(gulp.dest('build/dev'));
-}
-
-function startFakeServer() {
- const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]);
- fakeServer.stdout.on('data', (data) => {
+ const srv = spawn('node', args);
+ srv.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
- fakeServer.stderr.on('data', (data) => {
+ srv.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
+ return srv;
+}
+
+function startLocalServer(options = {}) {
+ connect.server({
+ https: argv.https,
+ port: port,
+ host: INTEG_SERVER_HOST,
+ root: './',
+ livereload: options.livereload
+ });
}
// Watch Task with Live Reload
@@ -372,13 +370,7 @@ function watchTaskMaker(options = {}) {
'modules/**/*.js',
].concat(options.alsoWatch));
- connect.server({
- https: argv.https,
- port: port,
- host: FAKE_SERVER_HOST,
- root: './',
- livereload: options.livereload
- });
+ startLocalServer(options);
mainWatcher.on('all', options.task());
done();
@@ -397,7 +389,7 @@ gulp.task(clean);
gulp.task(escapePostbidConfig);
gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, true)));
-gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, gulpBundle.bind(null, false)));
+gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, addBanner, gulpBundle.bind(null, false)));
// public tasks (dependencies are needed for each task since they can be ran on their own)
gulp.task('test-only', test);
@@ -414,11 +406,13 @@ 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', 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('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer)))
+gulp.task('serve-e2e-dev', gulp.series(clean, 'build-bundle-dev', gulp.parallel(() => startIntegServer(true), startLocalServer)))
-gulp.task('default', gulp.series(clean, makeWebpackPkg));
+gulp.task('default', gulp.series(clean, 'build-bundle-prod'));
-gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), injectFakeServerEndpoint, test));
+gulp.task('e2e-test-only', () => runWebdriver({file: argv.file}))
+gulp.task('e2e-test', gulp.series(clean, 'build-bundle-prod', testTaskMaker({e2e: true})));
// other tasks
gulp.task(bundleToStdout);
gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step
diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html
index e8920cf2ee1..fd61267479d 100644
--- a/integrationExamples/gpt/adloox.html
+++ b/integrationExamples/gpt/adloox.html
@@ -161,7 +161,11 @@
},
rubicon: {
singleRequest: true
- }
+ },
+ // RTD module honors pageUrl for referrer detection and
+ // the analytics module uses this for the 'pageurl' macro
+ // N.B. set this to a non-example.com URL to see the video
+ //pageUrl: 'https://yourdomain.com/some/path/to/content.html'
});
pbjs.enableAnalytics({
provider: 'adloox',
diff --git a/integrationExamples/gpt/esp_example.html b/integrationExamples/gpt/esp_example.html
new file mode 100644
index 00000000000..c39a67243cc
--- /dev/null
+++ b/integrationExamples/gpt/esp_example.html
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Basic Prebid.js Example
+
+ Div-1
+
+
+
+
+
+
+ Div-2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrationExamples/gpt/idward_segments_example.html b/integrationExamples/gpt/idward_segments_example.html
new file mode 100644
index 00000000000..9bc06124c77
--- /dev/null
+++ b/integrationExamples/gpt/idward_segments_example.html
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+Prebid.js Test
+Div-1
+
+
+
+First Party Data (ortb2) Sent to Bidding Adapter
+
+
+
diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html
index b6a22096c90..dbb4d2af0d6 100644
--- a/integrationExamples/gpt/permutiveRtdProvider_example.html
+++ b/integrationExamples/gpt/permutiveRtdProvider_example.html
@@ -45,6 +45,12 @@
}
},
bids: [
+ {
+ bidder: 'ix',
+ params: {
+ siteId: '123456',
+ }
+ },
{
bidder: 'appnexus',
params: {
@@ -135,6 +141,7 @@
pbjs.que.push(function() {
pbjs.setConfig({
debug: true,
+ pageUrl: 'http://www.test.com/test.html',
realTimeData: {
auctionDelay: 80, // maximum time for RTD modules to respond
dataProviders: [
@@ -142,8 +149,20 @@
name: 'permutive',
waitForIt: true,
params: {
- acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'],
+ acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx', 'ix'],
maxSegs: 500,
+ transformations: [
+ {
+ id: 'iab',
+ config: {
+ segtax: 4,
+ iabIds: {
+ 1000001: '777777',
+ 1000002: '888888'
+ }
+ }
+ }
+ ],
overwrites: {
rubicon: function (bid, data, acEnabled, utils, defaultFn) {
if (defaultFn){
@@ -160,7 +179,7 @@
}
});
pbjs.setBidderConfig({
- bidders: ['appnexus', 'rubicon'],
+ bidders: ['appnexus', 'rubicon', 'ix'],
config: {
ortb2: {
site: {
@@ -180,13 +199,9 @@
gender: 'm',
keywords: 'a,b',
data: [
- {
- name: 'www.dataprovider1.com',
- ext: { taxonomyname: 'iab_audience_taxonomy' },
- segment: [{ id: '687' }, { id: '123' }]
- },
{
name: 'permutive.com',
+ ext: { segtax: 6 },
segment: [{ id: '1' }]
}
]
diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html
index 585bb1c97ae..52578ebcada 100644
--- a/integrationExamples/gpt/userId_example.html
+++ b/integrationExamples/gpt/userId_example.html
@@ -77,6 +77,7 @@
"301": true, // zeotapIdPlus
"91": true, // criteo
"737": true, // amxId
+ "58": true, // 33acrossId
}
}
}
@@ -128,6 +129,17 @@
"expires": 30
}
},
+ {
+ "name": "33acrossId",
+ "params": {
+ "pid": '0'
+ },
+ "storage": {
+ "type": 'html5',
+ "name": '33acrossId',
+ "expires": 90
+ }
+ },
{
"name": "intentIqId",
"params": {
@@ -252,6 +264,9 @@
"params": {
"cid": 5126 // Set your Intimate Merger Customer ID here for production
}
+ },
+ {
+ "name": "dacId"
}
],
"syncDelay": 5000,
diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html
index b81ec52b2c4..73843c49914 100644
--- a/integrationExamples/gpt/weboramaRtdProvider_example.html
+++ b/integrationExamples/gpt/weboramaRtdProvider_example.html
@@ -1,9 +1,10 @@
-
+
+ weborama rtd submodule example
@@ -26,9 +27,8 @@
params: {
setPrebidTargeting: true, // optional
sendToBidders: true, // optional
- onData: function (data, site) { // optional
- var kind = (site) ? 'site' : 'user';
- console.log('onData', kind, data);
+ onData: function (data, meta) { // optional
+ console.log('onData', data, meta);
},
weboCtxConf: {
token: "to-be-defined", // mandatory
@@ -36,21 +36,30 @@
setPrebidTargeting: true, // override param.setPrebidTargeting or default true
sendToBidders: true, // override param.sendToBidders or default true
defaultProfile: { // optional
- webo_ctx: ['moon'],
+ webo_ctx: ["Rugby_Renault_c11495", "Sport_c11893"],
webo_ds: ['bar']
},
- //, onData: function (data, ...) { ...}
+ // enabled: false,
+ //, onData: function (data,...) { ...}
},
weboUserDataConf: {
- accountId: 12345, // optional
+ accountId: 12345, // recommended
setPrebidTargeting: true, // override param.setPrebidTargeting or default true
- sendToBidders: true, // override param.sendToBidders or default true
+ sendToBidders: ['smartadserver'], // specify the bidder to share data
defaultProfile: { // optional
- webo_cs: ['Red'],
+ webo_cs: ['red'],
webo_audiences: ['bam']
},
localStorageProfileKey: 'webo_wam2gam_entry', // default
+ // enabled: false,
//, onData: function (data,...) { ...}
+ },
+ sfbxLiteDataConf: {
+ enabled: true,
+ defaultProfile: { // optional
+ lite_occupation: ['gérant', 'bénévole'],
+ lite_hobbies: ['sport', 'cinéma'],
+ },
}
}
}]
@@ -62,6 +71,9 @@
var div_1_sizes = [
[300, 300]
];
+ var div_2_sizes = [
+ [600, 100]
+ ];
var PREBID_TIMEOUT = 3000;
var FAILSAFE_TIMEOUT = 5000;
@@ -106,6 +118,46 @@
networkId: 456456,
},
}]
+ },
+ {
+ code: '/1056029/webo-wam-prebid',
+ mediaTypes: {
+ banner: {
+ sizes: div_2_sizes
+ }
+ },
+ bids: [{
+ bidder: 'smartadserver',
+ params: {
+ siteId: 1234,
+ pageId: 1234,
+ formatId: 1234,
+ }
+ }, {
+ bidder: 'pubmatic',
+ params: {
+ publisherId: '32572',
+ }
+ }, {
+ bidder: 'appnexus',
+ params: {
+ placementId: 234234,
+ }
+ }, {
+ bidder: 'rubicon',
+ params: {
+ accountId: '14062',
+ siteId: '70608',
+ zoneId: '335918',
+ userId: '12346',
+ }
+ }, {
+ bidder: 'criteo',
+ params: {
+ zoneId: 234234,
+ networkId: 456456,
+ },
+ }]
}
];
@@ -138,7 +190,6 @@
});
}
-
// in case PBJS doesn't load
setTimeout(function () {
initAdserver();
@@ -146,6 +197,7 @@
googletag.cmd.push(function () {
googletag.defineSlot('/1056029/webo-ctx-prebid', div_1_sizes, 'div-gpt-ad-1620653642627-0').addService(googletag.pubads());
+ googletag.defineSlot('/1056029/webo-wam-prebid', div_2_sizes, 'div-gpt-ad-1645023761875-0').addService(googletag.pubads());
googletag.pubads().disableInitialLoad();
googletag.enableServices();
});
@@ -154,11 +206,12 @@
- test webo ctx using prebid.js
+ test webo rtd submodule with prebid.js
Basic Prebid.js Example
Div-1
+
+
diff --git a/karma.conf.maker.js b/karma.conf.maker.js
index be51947dae8..b5c6b44e4fd 100644
--- a/karma.conf.maker.js
+++ b/karma.conf.maker.js
@@ -2,6 +2,7 @@
//
// For more information, see http://karma-runner.github.io/1.0/config/configuration-file.html
+const babelConfig = require('./babelConfig.js');
var _ = require('lodash');
var webpackConf = require('./webpack.conf.js');
var karmaConstants = require('karma').constants;
@@ -10,10 +11,19 @@ function newWebpackConfig(codeCoverage) {
// Make a clone here because we plan on mutating this object, and don't want parallel tasks to trample each other.
var webpackConfig = _.cloneDeep(webpackConf);
- // remove optimize plugin for tests
- webpackConfig.plugins.pop()
+ Object.assign(webpackConfig, {
+ mode: 'development',
+ devtool: 'inline-source-map',
+ });
- webpackConfig.devtool = 'inline-source-map';
+ delete webpackConfig.entry;
+
+ webpackConfig.module.rules
+ .flatMap((r) => r.use)
+ .filter((use) => use.loader === 'babel-loader')
+ .forEach((use) => {
+ use.options = babelConfig(true);
+ });
if (codeCoverage) {
webpackConfig.module.rules.push({
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 2fb46377a64..59bae2013d1 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -1,12 +1,15 @@
{
"userId": [
+ "33acrossIdSystem",
"admixerIdSystem",
"adtelligentIdSystem",
"akamaiDAPIdSystem",
"amxIdSystem",
"britepoolIdSystem",
"connectIdSystem",
+ "cpexIdSystem",
"criteoIdSystem",
+ "dacIdSystem",
"deepintentDpesIdSystem",
"dmdIdSystem",
"fabrickIdSystem",
@@ -14,6 +17,7 @@
"hadronIdSystem",
"haloIdSystem",
"id5IdSystem",
+ "ftrackIdSystem",
"identityLinkIdSystem",
"idxIdSystem",
"imuIdSystem",
@@ -46,6 +50,7 @@
"dfpAdServerVideo"
],
"rtdModule": [
+ "airgridRtdProvider",
"browsiRtdProvider",
"dgkeywordRtdProvider",
"geoedgeRtdProvider",
diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js
index af67bb2bf48..498e6cf8634 100644
--- a/modules/33acrossBidAdapter.js
+++ b/modules/33acrossBidAdapter.js
@@ -20,6 +20,7 @@ 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';
const CURRENCY = 'USD';
+const GVLID = 58;
const GUID_PATTERN = /^[a-zA-Z0-9_-]{22}$/;
const PRODUCT = {
@@ -735,6 +736,7 @@ export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [ BANNER, VIDEO ],
+ gvlid: GVLID,
isBidRequestValid,
buildRequests,
interpretResponse,
diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js
new file mode 100644
index 00000000000..3763fee5124
--- /dev/null
+++ b/modules/33acrossIdSystem.js
@@ -0,0 +1,115 @@
+/**
+ * This module adds 33acrossId to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/33acrossIdSystem
+ * @requires module:modules/userId
+ */
+
+import { logMessage, logError } from '../src/utils.js';
+import { ajaxBuilder } from '../src/ajax.js';
+import { submodule } from '../src/hook.js';
+import { uspDataHandler } from '../src/adapterManager.js';
+
+const MODULE_NAME = '33acrossId';
+const API_URL = 'https://lexicon.33across.com/v1/envelope';
+const AJAX_TIMEOUT = 10000;
+
+function getEnvelope(response) {
+ if (!response.succeeded) {
+ logError(`${MODULE_NAME}: Unsuccessful response`);
+
+ return;
+ }
+
+ if (!response.data.envelope) {
+ logMessage(`${MODULE_NAME}: No envelope was received`);
+
+ return;
+ }
+
+ return response.data.envelope;
+}
+
+function calculateQueryStringParams(pid, gdprConsentData) {
+ const uspString = uspDataHandler.getConsentData();
+ const gdprApplies = Boolean(gdprConsentData?.gdprApplies);
+ const params = {
+ pid,
+ gdpr: Number(gdprApplies),
+ };
+
+ if (uspString) {
+ params.us_privacy = uspString;
+ }
+
+ if (gdprApplies) {
+ params.gdpr_consent = gdprConsentData.consentString || '';
+ }
+
+ return params;
+}
+
+/** @type {Submodule} */
+export const thirthyThreeAcrossIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: MODULE_NAME,
+
+ gvlid: 58,
+
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function
+ * @param {string} id
+ * @returns {{'33acrossId':{ envelope: string}}}
+ */
+ decode(id) {
+ return {
+ [MODULE_NAME]: {
+ envelope: id
+ }
+ };
+ },
+
+ /**
+ * performs action to obtain id and return a value in the callback's response argument
+ * @function
+ * @param {SubmoduleConfig} [config]
+ * @returns {IdResponse|undefined}
+ */
+ getId({ params = { } }, gdprConsentData) {
+ if (typeof params.pid !== 'string') {
+ logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`);
+
+ return;
+ }
+
+ const { pid, apiUrl = API_URL } = params;
+
+ return {
+ callback(cb) {
+ ajaxBuilder(AJAX_TIMEOUT)(apiUrl, {
+ success(response) {
+ let envelope;
+
+ try {
+ envelope = getEnvelope(JSON.parse(response))
+ } catch (err) {
+ logError(`${MODULE_NAME}: ID reading error:`, err);
+ }
+ cb(envelope);
+ },
+ error(err) {
+ logError(`${MODULE_NAME}: ID error response`, err);
+
+ cb();
+ }
+ }, calculateQueryStringParams(pid, gdprConsentData), { method: 'GET', withCredentials: true });
+ }
+ };
+ }
+};
+
+submodule('userId', thirthyThreeAcrossIdSubmodule);
diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md
new file mode 100644
index 00000000000..1e4af89344f
--- /dev/null
+++ b/modules/33acrossIdSystem.md
@@ -0,0 +1,53 @@
+# 33ACROSS ID
+
+For help adding this submodule, please contact [PrebidUIM@33across.com](PrebidUIM@33across.com).
+
+### Prebid Configuration
+
+You can configure this submodule in your `userSync.userIds[]` configuration:
+
+```javascript
+pbjs.setConfig({
+ userSync: {
+ userIds: [
+ {
+ name: "33acrossId",
+ storage: {
+ name: "33acrossId",
+ type: "html5",
+ expires: 90,
+ refreshInSeconds: 8*3600
+ },
+ params: {
+ pid: "0010b00002GYU4eBAH",
+ },
+ },
+ ],
+ },
+});
+```
+
+| Parameters under `userSync.userIds[]` | Scope | Type | Description | Example |
+| ---| --- | --- | --- | --- |
+| name | Required | String | Name for the 33Across ID submodule | `"33acrossId"` | |
+| storage | Required | Object | Configures how to cache User IDs locally in the browser | See [storage settings](#storage-settings) |
+| params | Required | Object | Parameters for 33Across ID submodule | See [params](#params) |
+
+### Storage Settings
+
+The following settings are available for the `storage` property in the `userSync.userIds[]` object:
+
+| Param name | Scope | Type | Description | Example |
+| --- | --- | --- | --- | --- |
+| name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` |
+| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` |
+| expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `90`. | `90` |
+| refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` |
+
+### Params
+
+The following settings are available in the `params` property in `userSync.userIds[]` object:
+
+| Param name | Scope | Type | Description | Example |
+| --- | --- | --- | --- | --- |
+| pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` |
diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js
index 3381f00ff8f..b98567878a8 100644
--- a/modules/adagioBidAdapter.js
+++ b/modules/adagioBidAdapter.js
@@ -1,18 +1,38 @@
-import find from 'core-js-pure/features/array/find.js';
+import {find} from '../src/polyfill.js';
import {
- isInteger, isArray, deepAccess, mergeDeep, logWarn, logInfo, logError, getWindowTop, getWindowSelf, generateUUID, _map,
- getDNT, parseUrl, getUniqueIdentifierStr, isNumber, cleanObj, isFn, inIframe, deepClone, getGptSlotInfoForAdUnitCode
+ _map,
+ cleanObj,
+ deepAccess,
+ deepClone,
+ generateUUID,
+ getDNT,
+ getGptSlotInfoForAdUnitCode,
+ getUniqueIdentifierStr,
+ getWindowSelf,
+ getWindowTop,
+ inIframe,
+ isArray,
+ isFn,
+ isInteger,
+ isNumber,
+ logError,
+ logInfo,
+ logWarn,
+ mergeDeep,
+ parseUrl
} from '../src/utils.js';
-import { config } from '../src/config.js';
+import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import { loadExternalScript } from '../src/adloader.js';
-import { verify } from 'criteo-direct-rsa-validate/build/verify.js';
-import { getStorageManager } from '../src/storageManager.js';
-import { getRefererInfo } from '../src/refererDetection.js';
-import { createEidsArray } from './userId/eids.js';
-import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
-import { Renderer } from '../src/Renderer.js';
-import { OUTSTREAM } from '../src/video.js';
+import {loadExternalScript} from '../src/adloader.js';
+import {verify} from 'criteo-direct-rsa-validate/build/verify.js';
+import {getStorageManager} from '../src/storageManager.js';
+import {getRefererInfo} from '../src/refererDetection.js';
+import {createEidsArray} from './userId/eids.js';
+import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {Renderer} from '../src/Renderer.js';
+import {OUTSTREAM} from '../src/video.js';
+import { getGlobal } from '../src/prebidGlobal.js';
+
const BIDDER_CODE = 'adagio';
const LOG_PREFIX = 'Adagio:';
const FEATURES_VERSION = '1';
@@ -21,13 +41,12 @@ const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO];
const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js';
const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript';
const GVLID = 617;
-export const storage = getStorageManager(GVLID, 'adagio');
+export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js';
const MAX_SESS_DURATION = 30 * 60 * 1000;
const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp';
const ADAGIO_PUBKEY_E = 65537;
const CURRENCY = 'USD';
-const DEFAULT_FLOOR = 0.1;
// This provide a whitelist and a basic validation
// of OpenRTB 2.5 options used by the Adagio SSP.
@@ -250,32 +269,14 @@ function getDevice() {
};
function getSite(bidderRequest) {
- let domain = '';
- let page = '';
- let referrer = '';
-
const { refererInfo } = bidderRequest;
-
- if (canAccessTopWindow()) {
- const wt = getWindowTop();
- domain = wt.location.hostname;
- page = wt.location.href;
- referrer = wt.document.referrer || '';
- } else if (refererInfo.reachedTop) {
- const url = parseUrl(refererInfo.referer);
- domain = url.hostname;
- page = refererInfo.referer;
- } 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.
- const url = parseUrl(refererInfo.stack[0]);
- domain = url.hostname;
- }
+ const url = parseUrl(refererInfo.referer);
return {
- domain,
- page,
- referrer
+ domain: url.hostname || '',
+ page: refererInfo.referer || '',
+ referrer: canAccessTopWindow() ? getWindowTop().document.referrer || '' : getWindowSelf().document.referrer || '',
+ top: refererInfo.reachedTop
};
};
@@ -570,13 +571,13 @@ function _getFloors(bidRequest) {
const info = bidRequest.getFloor({
currency: CURRENCY,
mediaType,
- size: []
+ size
});
floors.push(cleanObj({
mt: mediaType,
s: isArray(size) ? `${size[0]}x${size[1]}` : undefined,
- f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : DEFAULT_FLOOR
+ f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : undefined
}));
}
@@ -804,7 +805,7 @@ function getPrintNumber(adUnitCode, bidderRequest) {
return 1;
}
const adagioBid = find(bidderRequest.bids, bid => bid.adUnitCode === adUnitCode);
- return adagioBid.bidRequestsCount || 1;
+ return adagioBid.bidderRequestsCount || 1;
}
/**
@@ -850,7 +851,9 @@ function storeRequestInAdagioNS(bidRequest) {
}],
auctionId: bidRequest.auctionId,
pageviewId: internal.getPageviewId(),
- printNumber
+ printNumber,
+ localPbjs: '$$PREBID_GLOBAL$$',
+ localPbjsRef: getGlobal()
});
// (legacy) Store internal adUnit information
@@ -918,7 +921,45 @@ export const spec = {
});
// Handle priceFloors module
- bidRequest.floors = _getFloors(bidRequest);
+ const computedFloors = _getFloors(bidRequest);
+ if (isArray(computedFloors) && computedFloors.length) {
+ bidRequest.floors = computedFloors
+
+ if (deepAccess(bidRequest, 'mediaTypes.banner')) {
+ const bannerObj = bidRequest.mediaTypes.banner
+
+ const computeNewSizeArray = (sizeArr = []) => {
+ const size = { size: sizeArr, floor: null }
+ const bannerFloors = bidRequest.floors.filter(floor => floor.mt === BANNER)
+ const BannerSizeFloor = bannerFloors.find(floor => floor.s === sizeArr.join('x'))
+ size.floor = (bannerFloors) ? (BannerSizeFloor) ? BannerSizeFloor.f : bannerFloors[0].f : null
+ return size
+ }
+
+ // `bannerSizes`, internal property name
+ bidRequest.mediaTypes.banner.bannerSizes = (isArray(bannerObj.sizes[0]))
+ ? bannerObj.sizes.map(sizeArr => {
+ return computeNewSizeArray(sizeArr)
+ })
+ : computeNewSizeArray(bannerObj.sizes)
+ }
+
+ if (deepAccess(bidRequest, 'mediaTypes.video')) {
+ const videoObj = bidRequest.mediaTypes.video
+ const videoFloors = bidRequest.floors.filter(floor => floor.mt === VIDEO);
+ const playerSize = (videoObj.playerSize && isArray(videoObj.playerSize[0])) ? videoObj.playerSize[0] : videoObj.playerSize
+ const videoSizeFloor = (playerSize) ? videoFloors.find(floor => floor.s === playerSize.join('x')) : undefined
+
+ bidRequest.mediaTypes.video.floor = (videoFloors) ? videoSizeFloor ? videoSizeFloor.f : videoFloors[0].f : null
+ }
+
+ if (deepAccess(bidRequest, 'mediaTypes.native')) {
+ const nativeFloors = bidRequest.floors.filter(floor => floor.mt === NATIVE);
+ if (nativeFloors.length) {
+ bidRequest.mediaTypes.native.floor = nativeFloors[0].f
+ }
+ }
+ }
if (deepAccess(bidRequest, 'mediaTypes.video')) {
_buildVideoBidRequest(bidRequest);
@@ -937,6 +978,8 @@ export const spec = {
// remove useless props
delete adUnitCopy.floorData;
delete adUnitCopy.params.siteId;
+ delete adUnitCopy.userId
+ delete adUnitCopy.userIdAsEids
groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || [];
groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy);
diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js
index 1b93d4fe1c6..de8a3598be1 100644
--- a/modules/adbookpspBidAdapter.js
+++ b/modules/adbookpspBidAdapter.js
@@ -1,13 +1,24 @@
-import includes from 'core-js-pure/features/array/includes.js';
-import find from 'core-js-pure/features/array/find';
-import { config } from '../src/config.js';
-import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
-import { getStorageManager } from '../src/storageManager.js';
+import {find, includes} from '../src/polyfill.js';
+import {config} from '../src/config.js';
+import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {getStorageManager} from '../src/storageManager.js';
import {
- isPlainObject, deepSetValue, deepAccess, logWarn, inIframe, isNumber, logError, isArray, uniques,
- flatten, triggerPixel, isStr, isEmptyStr, generateUUID
+ deepAccess,
+ deepSetValue,
+ flatten,
+ generateUUID,
+ inIframe,
+ isArray,
+ isEmptyStr,
+ isNumber,
+ isPlainObject,
+ isStr,
+ logError,
+ logWarn,
+ triggerPixel,
+ uniques
} from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
/**
* CONSTANTS
@@ -577,7 +588,7 @@ function bannerHasSingleSize(bidRequest) {
* USER SYNC
*/
-export const storage = getStorageManager();
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) {
return responses
diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js
index f0425a174ff..0b9c72a2cee 100644
--- a/modules/adfBidAdapter.js
+++ b/modules/adfBidAdapter.js
@@ -206,6 +206,11 @@ export const spec = {
request.is_debug = !!test;
request.test = 1;
}
+
+ if (config.getConfig('coppa')) {
+ deepSetValue(request, 'regs.coppa', 1);
+ }
+
if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) {
deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1);
diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js
index 4dd320d3f24..e0d3a881cad 100644
--- a/modules/adgenerationBidAdapter.js
+++ b/modules/adgenerationBidAdapter.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.2.0';
+ const ADGENE_PREBID_VERSION = '1.3.0';
let serverRequests = [];
for (let i = 0, len = validBidRequests.length; i < len; i++) {
const validReq = validBidRequests[i];
@@ -50,6 +50,12 @@ export const spec = {
data = tryAppendQueryString(data, 'imark', '1');
}
data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.referer);
+ if (isIos()) {
+ const hyperId = getHyperId(validReq);
+ if (hyperId != null) {
+ data = tryAppendQueryString(data, 'hyper_id', hyperId);
+ }
+ }
// remove the trailing "&"
if (data.lastIndexOf('&') === data.length - 1) {
data = data.substring(0, data.length - 1);
@@ -263,4 +269,20 @@ function getCurrencyType() {
return 'JPY';
}
+/**
+ *
+ * @param validReq request
+ * @return {null|string}
+ */
+function getHyperId(validReq) {
+ if (validReq.userId && validReq.userId.novatiq && validReq.userId.novatiq.snowflake.syncResponse === 1) {
+ return validReq.userId.novatiq.snowflake.id;
+ }
+ return null;
+}
+
+function isIos() {
+ return (/(ios|ipod|ipad|iphone)/i).test(window.navigator.userAgent);
+}
+
registerBidder(spec);
diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js
index 6a8c98650c0..7f5af047993 100644
--- a/modules/adhashBidAdapter.js
+++ b/modules/adhashBidAdapter.js
@@ -1,8 +1,81 @@
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import includes from 'core-js-pure/features/array/includes.js';
-import { BANNER } from '../src/mediaTypes.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {includes} from '../src/polyfill.js';
+import {BANNER} from '../src/mediaTypes.js';
const VERSION = '1.0';
+const BAD_WORD_STEP = 0.1;
+const BAD_WORD_MIN = 0.2;
+
+/**
+ * Function that checks the page where the ads are being served for brand safety.
+ * If unsafe words are found the scoring of that page increases.
+ * If it becomes greater than the maximum allowed score false is returned.
+ * The rules may vary based on the website language or the publisher.
+ * The AdHash bidder will not bid on unsafe pages (according to 4A's).
+ * @param badWords list of scoring rules to chech against
+ * @param maxScore maximum allowed score for that bidding
+ * @returns boolean flag is the page safe
+ */
+function brandSafety(badWords, maxScore) {
+ /**
+ * Performs the ROT13 encoding on the string argument and returns the resulting string.
+ * The Adhash bidder uses ROT13 so that the response is not blocked by:
+ * - ad blocking software
+ * - parental control software
+ * - corporate firewalls
+ * due to the bad words contained in the response.
+ * @param value The input string.
+ * @returns string Returns the ROT13 version of the given string.
+ */
+ const rot13 = value => {
+ const input = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+ const output = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm';
+ const index = x => input.indexOf(x);
+ const translate = x => index(x) > -1 ? output[index(x)] : x;
+ return value.split('').map(translate).join('');
+ };
+
+ /**
+ * Calculates the scoring for each bad word with dimishing returns
+ * @param {integer} points points that this word costs
+ * @param {integer} occurances number of occurances
+ * @returns {float} final score
+ */
+ const scoreCalculator = (points, occurances) => {
+ let positive = true;
+ if (points < 0) {
+ points *= -1;
+ positive = false;
+ }
+ let result = 0;
+ for (let i = 0; i < occurances; i++) {
+ result += Math.max(points - i * BAD_WORD_STEP, BAD_WORD_MIN);
+ }
+ return positive ? result : -result;
+ };
+
+ // Default parameters if the bidder is unable to send some of them
+ badWords = badWords || [];
+ maxScore = parseInt(maxScore) || 10;
+
+ try {
+ let score = 0;
+ const content = window.top.document.body.innerText.toLowerCase();
+ const words = content.trim().split(/\s+/).length;
+ for (const [word, rule, points] of badWords) {
+ if (rule === 'full' && new RegExp('\\b' + rot13(word) + '\\b', 'i').test(content)) {
+ const occurances = content.match(new RegExp('\\b' + rot13(word) + '\\b', 'g')).length;
+ score += scoreCalculator(points, occurances);
+ } else if (rule === 'partial' && content.indexOf(rot13(word.toLowerCase())) > -1) {
+ const occurances = content.match(new RegExp(rot13(word), 'g')).length;
+ score += scoreCalculator(points, occurances);
+ }
+ }
+ return score < maxScore * words / 500;
+ } catch (e) {
+ return true;
+ }
+}
export const spec = {
code: 'adhash',
@@ -59,7 +132,8 @@ export const spec = {
blockedCreatives: [],
currentTimestamp: new Date().getTime(),
recentAds: [],
- GDPR: gdprConsent
+ GDPRApplies: gdprConsent ? gdprConsent.gdprApplies : null,
+ GDPR: gdprConsent ? gdprConsent.consentString : null
},
options: {
withCredentials: false,
@@ -73,7 +147,11 @@ export const spec = {
interpretResponse: (serverResponse, request) => {
const responseBody = serverResponse ? serverResponse.body : {};
- if (!responseBody.creatives || responseBody.creatives.length === 0) {
+ if (
+ !responseBody.creatives ||
+ responseBody.creatives.length === 0 ||
+ !brandSafety(responseBody.badWords, responseBody.maxScore)
+ ) {
return [];
}
diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js
index 2b4e67736f3..de5d59ca6f8 100644
--- a/modules/adkernelAdnAnalyticsAdapter.js
+++ b/modules/adkernelAdnAnalyticsAdapter.js
@@ -10,7 +10,7 @@ const GVLID = 14;
const ANALYTICS_VERSION = '1.0.2';
const DEFAULT_QUEUE_TIMEOUT = 4000;
const DEFAULT_HOST = 'tag.adkernel.com';
-const storageObj = getStorageManager(GVLID);
+const storageObj = getStorageManager({gvlid: GVLID});
const ADK_HB_EVENTS = {
AUCTION_INIT: 'auctionInit',
diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 0dcde7dff91..c2d6ca4d4dd 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -1,11 +1,26 @@
import {
- isStr, isArray, isPlainObject, deepSetValue, isNumber, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize,
- cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums, mergeDeep, isEmpty, inIframe
+ _each,
+ cleanObj,
+ contains,
+ createTrackPixelHtml,
+ deepAccess,
+ deepSetValue,
+ getAdUnitSizes,
+ getDNT,
+ inIframe,
+ isArray,
+ isArrayOfNums,
+ isEmpty,
+ isNumber,
+ isPlainObject,
+ isStr,
+ mergeDeep,
+ parseGPTSingleSizeArrayToRtbSize,
+ parseUrl
} from '../src/utils.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import find from 'core-js-pure/features/array/find.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {find, includes} from '../src/polyfill.js';
import {config} from '../src/config.js';
/*
diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js
index 781b8db830a..1091b87a22d 100644
--- a/modules/adlooxAnalyticsAdapter.js
+++ b/modules/adlooxAnalyticsAdapter.js
@@ -6,14 +6,27 @@
import adapterManager from '../src/adapterManager.js';
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 {loadExternalScript} from '../src/adloader.js';
+import {auctionManager} from '../src/auctionManager.js';
+import {AUCTION_COMPLETED} from '../src/auction.js';
import CONSTANTS from '../src/constants.json';
-import find from 'core-js-pure/features/array/find.js';
+import {find} from '../src/polyfill.js';
+import {getRefererInfo} from '../src/refererDetection.js';
import {
- deepAccess, logInfo, isPlainObject, logError, isStr, isNumber, getGptSlotInfoForAdUnitCode,
- isFn, mergeDeep, logMessage, insertElement, logWarn, getUniqueIdentifierStr, parseUrl
+ deepAccess,
+ getGptSlotInfoForAdUnitCode,
+ getUniqueIdentifierStr,
+ insertElement,
+ isFn,
+ isNumber,
+ isPlainObject,
+ isStr,
+ logError,
+ logInfo,
+ logMessage,
+ logWarn,
+ mergeDeep,
+ parseUrl
} from '../src/utils.js';
const MODULE = 'adlooxAnalyticsAdapter';
@@ -46,6 +59,10 @@ MACRO['targetelt'] = function(b, c) {
MACRO['creatype'] = function(b, c) {
return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY;
};
+MACRO['pageurl'] = function(b, c) {
+ const refererInfo = getRefererInfo();
+ return (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0];
+};
MACRO['pbadslot'] = function(b, c) {
const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code);
return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode;
diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md
index 916bc9a45ce..e09a6495730 100644
--- a/modules/adlooxAnalyticsAdapter.md
+++ b/modules/adlooxAnalyticsAdapter.md
@@ -129,6 +129,7 @@ The following macros are available
* `%%pbadslot%%`: [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit)
* it is recommended you read the [Prebid Ad Slot section in the Adloox RTD Provider documentation](./adlooxRtdProvider.md#prebid-ad-slot)
+ * `%%pageurl%%`: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer`
### Functions
diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js
index 34d1428ea1d..bb8334ec8fe 100644
--- a/modules/adlooxRtdProvider.js
+++ b/modules/adlooxRtdProvider.js
@@ -11,16 +11,29 @@
/* eslint standard/no-callback-literal: "off" */
/* eslint prebid/validate-imports: "off" */
-import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js';
-import { config as _config } from '../src/config.js';
-import { submodule } from '../src/hook.js';
-import { ajax } from '../src/ajax.js';
-import { getGlobal } from '../src/prebidGlobal.js';
+import {command as analyticsCommand, COMMAND} from './adlooxAnalyticsAdapter.js';
+import {config as _config} from '../src/config.js';
+import {submodule} from '../src/hook.js';
+import {ajax} from '../src/ajax.js';
+import {getGlobal} from '../src/prebidGlobal.js';
+import {getRefererInfo} from '../src/refererDetection.js';
import {
- getAdUnitSizes, logInfo, isPlainObject, logError, isStr, isInteger, isArray, isBoolean, mergeDeep, deepAccess,
- _each, deepSetValue, logWarn, getGptSlotInfoForAdUnitCode
+ _each,
+ deepAccess,
+ deepSetValue,
+ getAdUnitSizes,
+ getGptSlotInfoForAdUnitCode,
+ isArray,
+ isBoolean,
+ isInteger,
+ isPlainObject,
+ isStr,
+ logError,
+ logInfo,
+ logWarn,
+ mergeDeep
} from '../src/utils.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {includes} from '../src/polyfill.js';
const MODULE_NAME = 'adloox';
const MODULE = `${MODULE_NAME}RtdProvider`;
@@ -285,6 +298,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) {
}
}
+ const refererInfo = getRefererInfo();
const args = [
[ 'v', `pbjs-${getGlobal().version}` ],
[ 'c', config.params.clientid ],
@@ -293,7 +307,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) {
[ 'imp', config.params.imps ],
[ 'fc_ip', config.params.freqcap_ip ],
[ 'fc_ipua', config.params.freqcap_ipua ],
- [ 'pn', document.location.pathname ]
+ [ 'pn', (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0] ]
];
if (!adUnits.length) {
diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js
index 666e9aea309..21bcb6cee26 100644
--- a/modules/admanBidAdapter.js
+++ b/modules/admanBidAdapter.js
@@ -5,7 +5,7 @@ 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';
+const URL_SYNC = 'https://sync.admanmedia.com';
function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId ||
@@ -110,6 +110,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');
+ getUserId(placement.eids, bid.userId.idx, 'idx.lat');
}
if (traff === VIDEO) {
placement.playerSize = bid.mediaTypes[VIDEO].playerSize;
diff --git a/modules/admaruBidAdapter.js b/modules/admaruBidAdapter.js
new file mode 100644
index 00000000000..65f62c77e26
--- /dev/null
+++ b/modules/admaruBidAdapter.js
@@ -0,0 +1,81 @@
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER} from '../src/mediaTypes.js';
+
+const ADMARU_ENDPOINT = 'https://p1.admaru.net/AdCall';
+const BIDDER_CODE = 'admaru';
+
+const DEFAULT_BID_TTL = 360;
+
+function parseBid(rawBid, currency) {
+ const bid = {};
+
+ bid.cpm = rawBid.price;
+ bid.impid = rawBid.impid;
+ bid.requestId = rawBid.impid;
+ bid.netRevenue = true;
+ bid.dealId = '';
+ bid.creativeId = rawBid.crid;
+ bid.currency = currency;
+ bid.ad = rawBid.adm;
+ bid.width = rawBid.w;
+ bid.height = rawBid.h;
+ bid.mediaType = BANNER;
+ bid.ttl = DEFAULT_BID_TTL;
+
+ return bid;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER],
+
+ isBidRequestValid: function (bid) {
+ return !!(bid && bid.params && bid.params.pub_id && bid.params.adspace_id);
+ },
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ return validBidRequests.map(bid => {
+ const payload = {
+ pub_id: bid.params.pub_id,
+ adspace_id: bid.params.adspace_id,
+ bidderRequestId: bid.bidderRequestId,
+ bidId: bid.bidId
+ };
+
+ return {
+ method: 'GET',
+ url: ADMARU_ENDPOINT,
+ data: payload,
+ }
+ })
+ },
+
+ interpretResponse: function (serverResponse, bidRequest) {
+ const bidResponses = [];
+ let bid = null;
+
+ if (!serverResponse.hasOwnProperty('body') || !serverResponse.body.hasOwnProperty('seatbid')) {
+ return bidResponses;
+ }
+
+ const serverBody = serverResponse.body;
+ const seatbid = serverBody.seatbid;
+
+ for (let i = 0; i < seatbid.length; i++) {
+ if (!seatbid[i].hasOwnProperty('bid')) {
+ continue;
+ }
+
+ const innerBids = seatbid[i].bid;
+ for (let j = 0; j < innerBids.length; j++) {
+ bid = parseBid(innerBids[j], serverBody.cur);
+
+ bidResponses.push(bid);
+ }
+ }
+
+ return bidResponses;
+ }
+}
+
+registerBidder(spec);
diff --git a/modules/admaruBidAdapter.md b/modules/admaruBidAdapter.md
new file mode 100644
index 00000000000..9985a660ac6
--- /dev/null
+++ b/modules/admaruBidAdapter.md
@@ -0,0 +1,34 @@
+# Overview
+
+```
+Module Name: Admaru Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: support@admaru.com
+```
+
+# Description
+
+Module that connects to Admaru demand sources
+
+# Test Parameters
+```
+ var adUnits = [
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]], // a display size
+ }
+ },
+ bids: [
+ {
+ bidder: "admaru",
+ params: {
+ pub_id: '1234', // string - required
+ adspace_id: '1234' // string - required
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/modules/adnowBidAdapter.js b/modules/adnowBidAdapter.js
index badf57ed5c9..472d0fdb2e1 100644
--- a/modules/adnowBidAdapter.js
+++ b/modules/adnowBidAdapter.js
@@ -1,7 +1,7 @@
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { NATIVE, BANNER } from '../src/mediaTypes.js';
-import { parseSizesInput, deepAccess, parseQueryStringParameters } from '../src/utils.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, NATIVE} from '../src/mediaTypes.js';
+import {deepAccess, parseQueryStringParameters, parseSizesInput} from '../src/utils.js';
+import {includes} from '../src/polyfill.js';
const BIDDER_CODE = 'adnow';
const ENDPOINT = 'https://n.ads3-adnow.com/a';
diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js
index f05cd9f9f32..9e05ea664d8 100644
--- a/modules/adnuntiusBidAdapter.js
+++ b/modules/adnuntiusBidAdapter.js
@@ -27,7 +27,7 @@ const getSegmentsFromOrtb = function (ortb2) {
}
const handleMeta = function () {
- const storage = getStorageManager(GVLID, 'adnuntius')
+ const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE})
let adnMeta = null
if (storage.localStorageIsEnabled()) {
adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData'))
diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js
index e81f6e50054..40809c59093 100644
--- a/modules/adomikAnalyticsAdapter.js
+++ b/modules/adomikAnalyticsAdapter.js
@@ -1,11 +1,10 @@
import adapter from '../src/AnalyticsAdapter.js';
import CONSTANTS from '../src/constants.json';
import adapterManager from '../src/adapterManager.js';
-import { logInfo } from '../src/utils.js';
-import find from 'core-js-pure/features/array/find.js';
-import findIndex from 'core-js-pure/features/array/find-index.js';
+import {logInfo} from '../src/utils.js';
+import {find, findIndex} from '../src/polyfill.js';
-// Events used in adomik analytics adapter
+// Events used in adomik analytics adapter.
const auctionInit = CONSTANTS.EVENTS.AUCTION_INIT;
const auctionEnd = CONSTANTS.EVENTS.AUCTION_END;
const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED;
@@ -31,17 +30,13 @@ let adomikAdapter = Object.assign(adapter({}),
break;
case bidResponse:
- adomikAdapter.bucketEvents.push({
- type: 'response',
- event: adomikAdapter.buildBidResponse(args)
- });
+ adomikAdapter.saveBidResponse(args);
break;
case bidWon:
- adomikAdapter.sendWonEvent({
- id: args.adId,
- placementCode: args.adUnitCode
- });
+ args.id = args.adId;
+ args.placementCode = args.adUnitCode;
+ adomikAdapter.sendWonEvent(args);
break;
case bidRequested:
@@ -70,16 +65,28 @@ adomikAdapter.initializeBucketEvents = function() {
adomikAdapter.bucketEvents = [];
}
+adomikAdapter.saveBidResponse = function(args) {
+ let responseSaved = adomikAdapter.bucketEvents.find((bucketEvent) =>
+ bucketEvent.type == 'response' && bucketEvent.event.id == args.id
+ );
+ if (responseSaved) { return true; }
+ adomikAdapter.bucketEvents.push({
+ type: 'response',
+ event: adomikAdapter.buildBidResponse(args)
+ });
+}
+
adomikAdapter.maxPartLength = function () {
return (ua.includes(' MSIE ')) ? 1600 : 60000;
};
adomikAdapter.sendTypedEvent = function() {
+ let [testId, testValue] = adomikAdapter.getKeyValues();
const groupedTypedEvents = adomikAdapter.buildTypedEvents();
const bulkEvents = {
- testId: adomikAdapter.currentContext.testId,
- testValue: adomikAdapter.currentContext.testValue,
+ testId: testId,
+ testValue: testValue,
uid: adomikAdapter.currentContext.uid,
ahbaid: adomikAdapter.currentContext.id,
hostname: window.location.hostname,
@@ -115,10 +122,8 @@ adomikAdapter.sendTypedEvent = function() {
const stringBulkEvents = JSON.stringify(bulkEvents)
logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents);
- // Encode object in base64
const encodedBuf = window.btoa(stringBulkEvents);
- // Create final url and split it (+endpoint length)
const encodedUri = encodeURIComponent(encodedBuf);
const maxLength = adomikAdapter.maxPartLength();
const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g'));
@@ -131,16 +136,18 @@ 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)
+ let [testId, testValue] = adomikAdapter.getKeyValues();
+ let keyValues = { testId: testId, testValue: testValue };
+ let samplingInfo = { sampling: adomikAdapter.currentContext.sampling };
+ wonEvent = { ...adomikAdapter.buildBidResponse(wonEvent), ...keyValues, ...samplingInfo };
+
+ const stringWonEvent = JSON.stringify(wonEvent);
logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent);
- // Encode object in base64
const encodedBuf = window.btoa(stringWonEvent);
const encodedUri = encodeURIComponent(encodedBuf);
const img = new Image(1, 1);
- img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true`
+ img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true`;
}
adomikAdapter.buildBidResponse = function (bid) {
@@ -202,33 +209,49 @@ adomikAdapter.buildTypedEvents = function () {
return groupedTypedEvents;
}
-adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics;
+adomikAdapter.getKeyValues = function () {
+ let preventTest = sessionStorage.getItem(window.location.hostname + '_NoAdomikTest')
+ let inScope = sessionStorage.getItem(window.location.hostname + '_AdomikTestInScope')
+ let keyValues = JSON.parse(sessionStorage.getItem(window.location.hostname + '_AdomikTest'))
+ let testId;
+ let testValue;
+ if (typeof (keyValues) === 'object' && keyValues != undefined && !preventTest && inScope) {
+ testId = keyValues.testId
+ testValue = keyValues.testOptionLabel
+ }
+ return [testId, testValue]
+}
-adomikAdapter.enableAnalytics = function (config) {
- adomikAdapter.currentContext = {};
- const initOptions = config.options;
-
- _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);
- }
- } else {
- logInfo('Adomik Analytics ignored for sampling', config.sampling);
+adomikAdapter.enable = function(options) {
+ adomikAdapter.currentContext = {
+ uid: options.id,
+ url: options.url,
+ id: '',
+ timeouted: false,
+ sampling: options.sampling
}
+ logInfo('Adomik Analytics enabled with config', options);
+ adomikAdapter.adapterEnableAnalytics(options);
+};
+
+adomikAdapter.checkOptions = function(options) {
+ if (typeof options !== 'undefined') {
+ if (options.id && options.url) { adomikAdapter.enable(options); } else { logInfo('Adomik Analytics disabled because id and/or url is missing from config', options); }
+ } else { logInfo('Adomik Analytics disabled because config is missing'); }
+};
+
+adomikAdapter.checkSampling = function(options) {
+ _sampled = typeof options === 'undefined' ||
+ typeof options.sampling === 'undefined' ||
+ (options.sampling > 0 && Math.random() < parseFloat(options.sampling));
+ if (_sampled) { adomikAdapter.checkOptions(options) } else { logInfo('Adomik Analytics ignored for sampling', options.sampling); }
+};
+
+adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics;
+
+adomikAdapter.enableAnalytics = function ({ provider, options }) {
+ logInfo('Adomik Analytics enableAnalytics', provider);
+ adomikAdapter.checkSampling(options);
};
adapterManager.registerAnalyticsAdapter({
diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js
index b1adcd1311d..ac49f7ae32d 100644
--- a/modules/adotBidAdapter.js
+++ b/modules/adotBidAdapter.js
@@ -1,16 +1,16 @@
import {Renderer} from '../src/Renderer.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
-import {isStr, isFn, isArray, isPlainObject, isBoolean, logError, replaceAuctionPrice} from '../src/utils.js';
-import find from 'core-js-pure/features/array/find.js';
-import { config } from '../src/config.js';
+import {isArray, isBoolean, isFn, isPlainObject, isStr, logError, replaceAuctionPrice} from '../src/utils.js';
+import {find} from '../src/polyfill.js';
+import {config} from '../src/config.js';
import { OUTSTREAM } from '../src/video.js';
const BIDDER_CODE = 'adot';
const ADAPTER_VERSION = 'v2.0.0';
const BID_METHOD = 'POST';
const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest';
-const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols'];
+const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols'];
const DOMAIN_REGEX = new RegExp('//([^/]*)');
const FIRST_PRICE = 1;
const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative };
diff --git a/modules/adplusBidAdapter.js b/modules/adplusBidAdapter.js
index c001781a792..4707ca2ff5a 100644
--- a/modules/adplusBidAdapter.js
+++ b/modules/adplusBidAdapter.js
@@ -8,7 +8,7 @@ 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);
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
const COOKIE_EXP = 1000 * 60 * 60 * 24; // 1 day
// #endregion
diff --git a/modules/adpod.js b/modules/adpod.js
index eac8d8afdf0..b7c459fd66f 100644
--- a/modules/adpod.js
+++ b/modules/adpod.js
@@ -13,23 +13,36 @@
*/
import {
- generateUUID, deepAccess, logWarn, logInfo, isArrayOfNums, isArray, isNumber, logError, groupBy, compareOn,
- isPlainObject
+ compareOn,
+ deepAccess,
+ generateUUID,
+ groupBy,
+ isArray,
+ isArrayOfNums,
+ isNumber,
+ isPlainObject,
+ logError,
+ logInfo,
+ logWarn
} from '../src/utils.js';
-import { addBidToAuction, doCallbacksIfTimedout, AUCTION_IN_PROGRESS, callPrebidCache, getPriceByGranularity, getPriceGranularity } from '../src/auction.js';
-import { checkAdUnitSetup } from '../src/prebid.js';
-import { checkVideoBidSetup } from '../src/video.js';
-import { setupBeforeHookFnOnce, module } from '../src/hook.js';
-import { store } from '../src/videoCache.js';
-import { config } from '../src/config.js';
-import { ADPOD } from '../src/mediaTypes.js';
-import Set from 'core-js-pure/features/set';
-import find from 'core-js-pure/features/array/find.js';
-import { auctionManager } from '../src/auctionManager.js';
+import {
+ addBidToAuction,
+ AUCTION_IN_PROGRESS,
+ callPrebidCache,
+ doCallbacksIfTimedout,
+ getPriceByGranularity,
+ getPriceGranularity
+} from '../src/auction.js';
+import {checkAdUnitSetup} from '../src/prebid.js';
+import {checkVideoBidSetup} from '../src/video.js';
+import {module, setupBeforeHookFnOnce} from '../src/hook.js';
+import {store} from '../src/videoCache.js';
+import {config} from '../src/config.js';
+import {ADPOD} from '../src/mediaTypes.js';
+import {find, arrayFrom as from} from '../src/polyfill.js';
+import {auctionManager} from '../src/auctionManager.js';
import CONSTANTS from '../src/constants.json';
-const from = require('core-js-pure/features/array/from.js');
-
const TARGETING_KEY_PB_CAT_DUR = 'hb_pb_cat_dur';
const TARGETING_KEY_CACHE_ID = 'hb_cache_id';
diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js
index 2b5a7e15af2..d64874c393e 100644
--- a/modules/adprimeBidAdapter.js
+++ b/modules/adprimeBidAdapter.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 = 'adprime';
const AD_URL = 'https://delta.adprime.com/pbjs';
-const SYNC_URL = 'https://delta.adprime.com';
+const SYNC_URL = 'https://sync.adprime.com';
function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId ||
@@ -150,7 +151,8 @@ export const spec = {
},
getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
- let syncUrl = SYNC_URL
+ 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}`;
@@ -161,12 +163,15 @@ export const spec = {
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
}];
}
-
};
registerBidder(spec);
diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js
index ce31f64d705..348bdc90808 100644
--- a/modules/adqueryBidAdapter.js
+++ b/modules/adqueryBidAdapter.js
@@ -11,7 +11,7 @@ const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUER
const ADQUERY_DEFAULT_CURRENCY = 'PLN';
const ADQUERY_NET_REVENUE = true;
const ADQUERY_TTL = 360;
-const storage = getStorageManager(ADQUERY_GVLID);
+const storage = getStorageManager({gvlid: ADQUERY_GVLID, bidderCode: ADQUERY_BIDDER_CODE});
/** @type {BidderSpec} */
export const spec = {
diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js
index 5357c1a1ffd..85421bf588d 100644
--- a/modules/adqueryIdSystem.js
+++ b/modules/adqueryIdSystem.js
@@ -13,7 +13,7 @@ import * as utils from '../src/utils.js';
const MODULE_NAME = 'qid';
const AU_GVLID = 902;
-export const storage = getStorageManager(AU_GVLID, 'qid');
+export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'qid'});
/**
* Param or default.
diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js
index 649031d1e3b..3d4de7c7b9d 100644
--- a/modules/adrelevantisBidAdapter.js
+++ b/modules/adrelevantisBidAdapter.js
@@ -1,14 +1,26 @@
-import { Renderer } from '../src/Renderer.js';
+import {Renderer} from '../src/Renderer.js';
import {
- logError, convertTypes, convertCamelToUnderscore, isArray, deepClone, logWarn, logMessage, getBidRequest, deepAccess,
- isStr, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, chunk, isArrayOfNums
+ chunk,
+ convertCamelToUnderscore,
+ convertTypes,
+ createTrackPixelHtml,
+ deepAccess,
+ deepClone,
+ getBidRequest,
+ isArray,
+ isArrayOfNums,
+ isEmpty,
+ isStr,
+ logError,
+ logMessage,
+ logWarn,
+ transformBidderParamKeywords
} 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 find from 'core-js-pure/features/array/find.js';
-import includes from 'core-js-pure/features/array/includes.js';
-import { OUTSTREAM, INSTREAM } from '../src/video.js';
+import {config} from '../src/config.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {find, includes} from '../src/polyfill.js';
+import {INSTREAM, OUTSTREAM} from '../src/video.js';
const BIDDER_CODE = 'adrelevantis';
const URL = 'https://ssp.adrelevantis.com/prebid';
@@ -127,7 +139,7 @@ export const spec = {
if (fpdcfg && fpdcfg.context) {
let fdata = {
keywords: fpdcfg.context.keywords || '',
- category: fpdcfg.context.category || ''
+ category: fpdcfg.context.data.category || ''
}
payload.fpd = fdata;
}
diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js
new file mode 100644
index 00000000000..4520066c3e7
--- /dev/null
+++ b/modules/adrinoBidAdapter.js
@@ -0,0 +1,74 @@
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {triggerPixel} from '../src/utils.js';
+import {NATIVE} from '../src/mediaTypes.js';
+
+const BIDDER_CODE = 'adrino';
+const REQUEST_METHOD = 'POST';
+const BIDDER_HOST = 'https://prd-prebid-bidder.adrino.io';
+const GVLID = 1072;
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: GVLID,
+ supportedMediaTypes: [NATIVE],
+
+ isBidRequestValid: function (bid) {
+ return !!(bid.bidId) &&
+ !!(bid.params) &&
+ !!(bid.params.hash) &&
+ (typeof bid.params.hash === 'string') &&
+ !!(bid.mediaTypes) &&
+ Object.keys(bid.mediaTypes).includes(NATIVE) &&
+ (bid.bidder === BIDDER_CODE);
+ },
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ const bidRequests = [];
+
+ for (let i = 0; i < validBidRequests.length; i++) {
+ let requestData = {
+ bidId: validBidRequests[i].bidId,
+ nativeParams: validBidRequests[i].nativeParams,
+ placementHash: validBidRequests[i].params.hash,
+ referer: bidderRequest.refererInfo.referer,
+ userAgent: navigator.userAgent,
+ }
+
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ requestData.gdprConsent = {
+ consentString: bidderRequest.gdprConsent.consentString,
+ consentRequired: bidderRequest.gdprConsent.gdprApplies
+ }
+ }
+
+ bidRequests.push({
+ method: REQUEST_METHOD,
+ url: BIDDER_HOST + '/bidder/bid/',
+ data: requestData,
+ options: {
+ contentType: 'application/json',
+ withCredentials: false,
+ }
+ });
+ }
+
+ return bidRequests;
+ },
+
+ interpretResponse: function (serverResponse, bidRequest) {
+ const response = serverResponse.body;
+ const bidResponses = [];
+ if (!response.noAd) {
+ bidResponses.push(response);
+ }
+ return bidResponses;
+ },
+
+ onBidWon: function (bid) {
+ if (bid['requestId']) {
+ triggerPixel(BIDDER_HOST + '/bidder/won/' + bid['requestId']);
+ }
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/adrinoBidAdapter.md b/modules/adrinoBidAdapter.md
new file mode 100644
index 00000000000..5ec63a72736
--- /dev/null
+++ b/modules/adrinoBidAdapter.md
@@ -0,0 +1,45 @@
+# Overview
+
+```
+Module Name: Adrino Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: dev@adrino.pl
+```
+
+# Description
+
+Module connects to Adrino bidder to fetch bids. Only native format is supported.
+
+# Test Parameters
+
+```
+var adUnits = [
+ code: '/12345678/prebid_native_example_1',
+ mediaTypes: {
+ native: {
+ image: {
+ required: true,
+ sizes: [[300, 210],[300,150],[140,100]]
+ },
+ title: {
+ required: true
+ },
+ sponsoredBy: {
+ required: false
+ },
+ body: {
+ required: false
+ },
+ icon: {
+ required: false
+ }
+ }
+ },
+ bids: [{
+ bidder: 'adrino',
+ params: {
+ hash: 'abcdef123456'
+ }
+ }]
+];
+```
diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js
index 67e039e4692..5ab417520e9 100644
--- a/modules/adriverBidAdapter.js
+++ b/modules/adriverBidAdapter.js
@@ -1,13 +1,14 @@
// ADRIVER BID ADAPTER for Prebid 1.13
import { logInfo, getWindowLocation, getBidIdParameter, _each } from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
+import { getStorageManager } from '../src/storageManager.js';
const BIDDER_CODE = 'adriver';
const ADRIVER_BID_URL = 'https://pb.adriver.ru/cgi-bin/bid.cgi';
const TIME_TO_LIVE = 3000;
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
-
code: BIDDER_CODE,
/**
@@ -98,6 +99,15 @@ export const spec = {
});
});
+ let userid = validBidRequests[0].userId;
+ let adrcidCookie = storage.getDataFromLocalStorage('adrcid') || validBidRequests[0].userId.adrcid;
+
+ if (adrcidCookie) {
+ payload.adrcid = adrcidCookie;
+ payload.id5 = userid.id5id;
+ payload.sharedid = userid.pubcid;
+ payload.unifiedid = userid.tdid;
+ }
const payloadString = JSON.stringify(payload);
return {
diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js
new file mode 100644
index 00000000000..6a492fac508
--- /dev/null
+++ b/modules/adriverIdSystem.js
@@ -0,0 +1,83 @@
+/**
+ * This module adds AdriverId to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/adriverIdSubmodule
+ * @requires module:modules/userId
+ */
+
+import { logError, isPlainObject } from '../src/utils.js'
+import { ajax } from '../src/ajax.js';
+import { submodule } from '../src/hook.js';
+import { getStorageManager } from '../src/storageManager.js';
+
+const MODULE_NAME = 'adriverId';
+
+export const storage = getStorageManager();
+
+/** @type {Submodule} */
+export const adriverIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: MODULE_NAME,
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function
+ * @param {string} value
+ * @returns {{adriverId:string}}
+ */
+ decode(value) {
+ return { adrcid: value }
+ },
+ /**
+ * performs action to obtain id and return a value in the callback's response argument
+ * @function
+ * @param {SubmoduleConfig} [config]
+ * @param {ConsentData} [consentData]
+ * @returns {IdResponse|undefined}
+ */
+ getId(config) {
+ if (!isPlainObject(config.params)) {
+ config.params = {};
+ }
+ const url = 'https://ad.adriver.ru/cgi-bin/json.cgi?sid=1&ad=719473&bt=55&pid=3198680&bid=7189165&bn=7189165&tuid=1';
+ const resp = function (callback) {
+ let creationDate = storage.getDataFromLocalStorage('adrcid_cd') || storage.getCookie('adrcid_cd');
+ let cookie = storage.getDataFromLocalStorage('adrcid') || storage.getCookie('adrcid');
+
+ if (cookie && creationDate && ((new Date().getTime() - creationDate) < 86400000)) {
+ const responseObj = cookie;
+ callback(responseObj);
+ } else {
+ const callbacks = {
+ success: response => {
+ let responseObj;
+ if (response) {
+ try {
+ responseObj = JSON.parse(response).adrcid;
+ } catch (error) {
+ logError(error);
+ }
+ let now = new Date();
+ now.setTime(now.getTime() + 86400 * 1825 * 1000);
+ storage.setCookie('adrcid', responseObj, now.toUTCString(), 'Lax');
+ storage.setDataInLocalStorage('adrcid', responseObj);
+ storage.setCookie('adrcid_cd', new Date().getTime(), now.toUTCString(), 'Lax');
+ storage.setDataInLocalStorage('adrcid_cd', new Date().getTime());
+ }
+ callback(responseObj);
+ },
+ error: error => {
+ logError(`${MODULE_NAME}: ID fetch encountered an error`, error);
+ callback();
+ }
+ };
+ ajax(url, callbacks, undefined, {method: 'GET'});
+ }
+ };
+ return {callback: resp};
+ }
+};
+
+submodule('userId', adriverIdSubmodule);
diff --git a/modules/adriverIdSystem.md b/modules/adriverIdSystem.md
new file mode 100644
index 00000000000..797318ba977
--- /dev/null
+++ b/modules/adriverIdSystem.md
@@ -0,0 +1,19 @@
+# Overview
+
+Module Name: AdRiver Id System
+Module Type: User Id System
+Maintainer: support@adriver.ru
+
+# Description
+
+Adriver user identification system
+
+## Example configuration for publishers:
+
+pbjs.setConfig({
+ userSync: {
+ userIds: [{
+ name: 'adriverId'
+ }]
+ }
+});
\ No newline at end of file
diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js
index 0ad0177815a..a07b0de0f67 100644
--- a/modules/adtargetBidAdapter.js
+++ b/modules/adtargetBidAdapter.js
@@ -1,8 +1,8 @@
-import { deepAccess, isArray, chunk, _map, flatten, logError, parseSizesInput } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { config } from '../src/config.js';
-import find from 'core-js-pure/features/array/find.js';
+import {_map, chunk, deepAccess, flatten, isArray, logError, parseSizesInput} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
+import {find} from '../src/polyfill.js';
const ENDPOINT = 'https://ghb.console.adtarget.com.tr/v2/auction/';
const BIDDER_CODE = 'adtarget';
diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js
index 44a9c90d438..13174ff337c 100644
--- a/modules/adtelligentBidAdapter.js
+++ b/modules/adtelligentBidAdapter.js
@@ -1,9 +1,9 @@
-import { deepAccess, isArray, chunk, _map, flatten, convertTypes, parseSizesInput } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { ADPOD, BANNER, VIDEO } from '../src/mediaTypes.js';
-import { config } from '../src/config.js';
-import { Renderer } from '../src/Renderer.js';
-import find from 'core-js-pure/features/array/find.js';
+import {_map, chunk, convertTypes, deepAccess, flatten, isArray, parseSizesInput} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
+import {Renderer} from '../src/Renderer.js';
+import {find} from '../src/polyfill.js';
const subdomainSuffixes = ['', 1, 2];
const AUCTION_PATH = '/v2/auction/';
@@ -18,9 +18,10 @@ const HOST_GETTERS = {
navelix: () => 'ghb.hb.navelix.com',
appaloosa: () => 'ghb.hb.appaloosa.media',
onefiftytwomedia: () => 'ghb.ads.152media.com',
- mediafuse: () => 'ghb.hbmp.mediafuse.com',
bidsxchange: () => 'ghb.hbd.bidsxchange.com',
streamkey: () => 'ghb.hb.streamkey.net',
+ janet: () => 'ghb.bidder.jmgads.com',
+ pgam: () => 'ghb.pgamssp.com',
}
const getUri = function (bidderCode) {
let bidderWithoutSuffix = bidderCode.split('_')[0];
@@ -36,12 +37,9 @@ const syncsCache = {};
export const spec = {
code: BIDDER_CODE,
gvlid: 410,
- aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey',
+ aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet',
{ code: 'navelix', gvlid: 380 },
- {
- code: 'mediafuse',
- skipPbsAliasing: true
- }
+ 'pgam'
],
supportedMediaTypes: [VIDEO, BANNER],
isBidRequestValid: function (bid) {
diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js
index df848fba823..283e1273150 100644
--- a/modules/adtrueBidAdapter.js
+++ b/modules/adtrueBidAdapter.js
@@ -4,8 +4,8 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
import {config} from '../src/config.js';
import {getStorageManager} from '../src/storageManager.js';
-const storage = getStorageManager();
const BIDDER_CODE = 'adtrue';
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
const ADTRUE_CURRENCY = 'USD';
const ENDPOINT_URL = 'https://hb.adtrue.com/prebid/auction';
const LOG_WARN_PREFIX = 'AdTrue: ';
diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js
index 854c65b1f22..605e19cfc66 100755
--- a/modules/advangelistsBidAdapter.js
+++ b/modules/advangelistsBidAdapter.js
@@ -1,9 +1,8 @@
-import { isEmpty, deepAccess, isFn, parseSizesInput, generateUUID, parseUrl } from '../src/utils.js';
-import { config } from '../src/config.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-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';
+import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js';
+import {config} from '../src/config.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {find, includes} from '../src/polyfill.js';
const ADAPTER_VERSION = '1.0';
const BIDDER_CODE = 'advangelists';
diff --git a/modules/adxpremiumAnalyticsAdapter.js b/modules/adxpremiumAnalyticsAdapter.js
index 3e30de14052..9066c26fb00 100644
--- a/modules/adxpremiumAnalyticsAdapter.js
+++ b/modules/adxpremiumAnalyticsAdapter.js
@@ -1,9 +1,9 @@
-import { logError, logInfo, deepClone } from '../src/utils.js';
-import { ajax } from '../src/ajax.js';
+import {deepClone, logError, logInfo} from '../src/utils.js';
+import {ajax} from '../src/ajax.js';
import adapter from '../src/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
-import includes from 'core-js-pure/features/array/includes.js';
+import {includes} from '../src/polyfill.js';
const analyticsType = 'endpoint';
const defaultUrl = 'https://adxpremium.services/graphql';
diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js
index 7e4c3a729e5..6fcce753596 100644
--- a/modules/adyoulikeBidAdapter.js
+++ b/modules/adyoulikeBidAdapter.js
@@ -1,8 +1,8 @@
-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 {buildUrl, deepAccess, 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 '../src/polyfill.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
const VERSION = '1.0';
@@ -61,6 +61,7 @@ export const spec = {
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (bidRequests, bidderRequest) {
+ let hasVideo = false;
const payload = {
Version: VERSION,
Bids: bidRequests.reduce((accumulator, bidReq) => {
@@ -88,6 +89,7 @@ export const spec = {
accumulator[bidReq.bidId].Native = nativeReq;
}
if (mediatype === VIDEO) {
+ hasVideo = true;
accumulator[bidReq.bidId].Video = bidReq.mediaTypes.video;
const size = bidReq.mediaTypes.video.playerSize;
@@ -122,7 +124,7 @@ export const spec = {
return {
method: 'POST',
- url: createEndpoint(bidRequests, bidderRequest),
+ url: createEndpoint(bidRequests, bidderRequest, hasVideo),
data,
options
};
@@ -217,12 +219,13 @@ function getPageRefreshed() {
}
/* Create endpoint url */
-function createEndpoint(bidRequests, bidderRequest) {
+function createEndpoint(bidRequests, bidderRequest, hasVideo) {
let host = getHostname(bidRequests);
+ const endpoint = hasVideo ? '/hb-api/prebid-video/v1' : '/hb-api/prebid/v1';
return buildUrl({
protocol: 'https',
host: `${DEFAULT_DC}${host}.omnitagjs.com`,
- pathname: '/hb-api/prebid/v1',
+ pathname: endpoint,
search: createEndpointQS(bidderRequest)
});
}
@@ -347,14 +350,6 @@ function getTrackers(eventsArray, jsTrackers) {
return result;
}
-function getVideoAd(response) {
- var adJson = {};
- 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');
- }
-}
-
function getNativeAssets(response, nativeConfig) {
if (typeof response.Native === 'object') {
return response.Native;
@@ -483,8 +478,10 @@ function createBid(response, bidRequests) {
};
// retreive video response if present
- const vast64 = response.Vast || getVideoAd(response);
+ const vast64 = response.Vast;
if (vast64) {
+ bid.width = response.Width;
+ bid.height = response.Height;
bid.vastXml = window.atob(vast64);
bid.mediaType = 'video';
} else if (request.Native) {
diff --git a/modules/afpBidAdapter.js b/modules/afpBidAdapter.js
index 68941ff17c9..6565942bcc8 100644
--- a/modules/afpBidAdapter.js
+++ b/modules/afpBidAdapter.js
@@ -1,7 +1,7 @@
-import includes from 'core-js-pure/features/array/includes.js'
-import { registerBidder } from '../src/adapters/bidderFactory.js'
-import { Renderer } from '../src/Renderer.js'
-import { BANNER, VIDEO } from '../src/mediaTypes.js'
+import {includes} from '../src/polyfill.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {Renderer} from '../src/Renderer.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
export const IS_DEV = location.hostname === 'localhost'
export const BIDDER_CODE = 'afp'
diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js
index f5403cca3eb..e9011343a74 100644
--- a/modules/airgridRtdProvider.js
+++ b/modules/airgridRtdProvider.js
@@ -5,18 +5,26 @@
* @module modules/airgridRtdProvider
* @requires module:modules/realTimeData
*/
-import {config} from '../src/config.js';
-import {submodule} from '../src/hook.js';
-import {mergeDeep, isPlainObject, deepSetValue, deepAccess} from '../src/utils.js';
-import {getGlobal} from '../src/prebidGlobal.js';
-import {getStorageManager} from '../src/storageManager.js';
+import { config } from '../src/config.js';
+import { submodule } from '../src/hook.js';
+import {
+ mergeDeep,
+ isPlainObject,
+ deepSetValue,
+ deepAccess,
+} from '../src/utils.js';
+import { getGlobal } from '../src/prebidGlobal.js';
+import { getStorageManager } from '../src/storageManager.js';
const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'airgrid';
const AG_TCF_ID = 782;
-export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'
+export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids';
-export const storage = getStorageManager(AG_TCF_ID, SUBMODULE_NAME);
+export const storage = getStorageManager({
+ gvlid: AG_TCF_ID,
+ moduleName: SUBMODULE_NAME,
+});
/**
* Attach script tag to DOM
@@ -24,13 +32,13 @@ export const storage = getStorageManager(AG_TCF_ID, SUBMODULE_NAME);
* @return {void}
*/
export function attachScriptTagToDOM(rtdConfig) {
- var edktInitializor = window.edktInitializor = window.edktInitializor || {};
+ var edktInitializor = (window.edktInitializor = window.edktInitializor || {});
if (!edktInitializor.invoked) {
edktInitializor.invoked = true;
edktInitializor.accountId = rtdConfig.params.accountId;
edktInitializor.publisherId = rtdConfig.params.publisherId;
edktInitializor.apiKey = rtdConfig.params.apiKey;
- edktInitializor.load = function(e) {
+ edktInitializor.load = function (e) {
var p = e || 'sdk';
var n = document.createElement('script');
n.type = 'module';
@@ -48,7 +56,7 @@ export function attachScriptTagToDOM(rtdConfig) {
*/
export function getMatchedAudiencesFromStorage() {
const audiences = storage.getDataFromLocalStorage(AG_AUDIENCE_IDS_KEY);
- if (!audiences) return []
+ if (!audiences) return [];
try {
return JSON.parse(audiences);
} catch (e) {
@@ -68,8 +76,8 @@ function setAudiencesToAppNexusAdUnits(adUnits, audiences) {
if (bid.bidder && bid.bidder === 'appnexus') {
deepSetValue(bid, 'params.keywords.perid', audiences || []);
}
- })
- })
+ });
+ });
}
/**
@@ -82,7 +90,7 @@ export function setAudiencesUsingBidderOrtb2(rtdConfig, audiences) {
const bidders = deepAccess(rtdConfig, 'params.bidders');
if (!bidders || bidders.length === 0) return;
const allBiddersConfig = config.getBidderConfig();
- const agOrtb2 = {}
+ const agOrtb2 = {};
deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []);
bidders.forEach((bidder) => {
@@ -92,11 +100,19 @@ export function setAudiencesUsingBidderOrtb2(rtdConfig, audiences) {
}
config.setBidderConfig({
bidders: [bidder],
- config: mergeDeep(bidderConfig, agOrtb2)
+ config: mergeDeep(bidderConfig, agOrtb2),
});
});
}
+export function setAudiencesUsingAppNexusAuctionKeywords(audiences) {
+ config.setConfig({
+ appnexusAuctionKeywords: {
+ perid: audiences,
+ },
+ });
+}
+
/**
* Module init
* @param {Object} rtdConfig
@@ -116,23 +132,29 @@ function init(rtdConfig, userConsent) {
* @param {Object} userConsent
* @return {void}
*/
-export function passAudiencesToBidders(bidConfig, onDone, rtdConfig, userConsent) {
+export function passAudiencesToBidders(
+ bidConfig,
+ onDone,
+ rtdConfig,
+ userConsent
+) {
const adUnits = bidConfig.adUnits || getGlobal().adUnits;
const audiences = getMatchedAudiencesFromStorage();
if (audiences.length > 0) {
+ setAudiencesUsingAppNexusAuctionKeywords(audiences);
setAudiencesUsingBidderOrtb2(rtdConfig, audiences);
if (adUnits) {
setAudiencesToAppNexusAdUnits(adUnits, audiences);
}
}
onDone();
-};
+}
/** @type {RtdSubmodule} */
export const airgridSubmodule = {
name: SUBMODULE_NAME,
init: init,
- getBidRequestData: passAudiencesToBidders
+ getBidRequestData: passAudiencesToBidders,
};
submodule(MODULE_NAME, airgridSubmodule);
diff --git a/modules/airgridRtdProvider.md b/modules/airgridRtdProvider.md
index 7ee502b4c10..6251c63fce9 100644
--- a/modules/airgridRtdProvider.md
+++ b/modules/airgridRtdProvider.md
@@ -1,15 +1,17 @@
- ---
- layout: page_v2
- title: AirGrid RTD SubModule
- description: Client-side, cookieless and privacy-first audiences.
- page_type: module
- module_type: rtd
- module_code : example
- enable_download : true
- sidebarType : 1
- ---
-
-# AirGrid
+---
+layout: page_v2
+title: AirGrid RTD Provider
+display_name: AirGrid RTD Provider
+description: Client-side, cookieless and privacy-first audiences.
+page_type: module
+module_type: rtd
+module_code : airgridRtdProvider
+enable_download : true
+vendor_specific: true
+sidebarType : 1
+---
+
+# AirGrid RTD Provider
AirGrid is a privacy-first, cookie-less audience platform. Designed to help publishers increase inventory yield,
whilst providing audience signal to buyers in the bid request, without exposing raw user level data to any party.
@@ -17,13 +19,17 @@ whilst providing audience signal to buyers in the bid request, without exposing
This real-time data module provides quality first-party data, contextual data, site-level data and more that is
injected into bid request objects destined for different bidders in order to optimize targeting.
+{:.no_toc}
+* TOC
+{:toc}
+
## Usage
-Compile the Halo RTD module into your Prebid build:
+Compile the AirGrid RTD module (`airgridRtdProvider`) into your Prebid build, along with the parent RTD Module (`rtdModule`):
`gulp build --modules=rtdModule,airgridRtdProvider,appnexusBidAdapter`
-Add the AirGrid RTD provider to your Prebid config. In this example we will configure publisher 1234 to retrieve segments from Audigent. See the "Parameter Descriptions" below for more detailed information of the configuration parameters.
+Next we configure the module, via `pbjs.setConfig`. See the **Parameter Descriptions** below for more detailed information of the configuration parameters.
```js
pbjs.setConfig(
@@ -50,6 +56,7 @@ pbjs.setConfig(
### Parameter Descriptions
+{: .table .table-bordered .table-striped }
| Name |Type | Description | Notes |
| :------------ | :------------ | :------------ |:------------ |
| name | `String` | RTD sub module name | Always 'airgrid' |
@@ -61,7 +68,7 @@ pbjs.setConfig(
_Note: Although the module supports passing segment data to any bidder using the ORTB2 spec, there is no way for this to be currently monetised. Please reach out to support, to discuss using bidders other than Xandr/AppNexus._
-If you do not have your own `apiKey`, `accountId` & `publisherId` please reach out to [support@airgrid.io](mailto:support@airgrid.io)
+If you do not have your own `apiKey`, `accountId` & `publisherId` please reach out to [support@airgrid.io](mailto:support@airgrid.io) or you can sign up via the [AirGrid platform](https://app.airgrid.io).
## Testing
@@ -89,7 +96,7 @@ If you require further assistance or are interested in discussing the module fun
- [hello@airgrid.io](mailto:hello@airgrid.io) for general questions.
- [support@airgrid.io](mailto:support@airgrid.io) for technical questions.
-You are also able to find more examples and other integration routes on the [AirGrid docs site](docs.airgrid.io).
+You are also able to find more examples and other integration routes on the [AirGrid docs site](https://docs.airgrid.io), or learn more on our [site](https://airgrid.io)!
Happy Coding! 😊
The AirGrid Team.
diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js
index d143a53fbf4..aca984d39c8 100644
--- a/modules/akamaiDapRtdProvider.js
+++ b/modules/akamaiDapRtdProvider.js
@@ -15,7 +15,7 @@ const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'dap';
export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments';
-export const storage = getStorageManager(null, SUBMODULE_NAME);
+export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME});
/**
* Lazy merge objects.
diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js
new file mode 100644
index 00000000000..261fd9dee68
--- /dev/null
+++ b/modules/alkimiBidAdapter.js
@@ -0,0 +1,119 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { deepClone, deepAccess } from '../src/utils.js';
+import { ajax } from '../src/ajax.js';
+import { VIDEO } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
+
+const BIDDER_CODE = 'alkimi';
+export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true';
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: ['banner', 'video'],
+
+ isBidRequestValid: function (bid) {
+ return !!(bid.params && bid.params.bidFloor && bid.params.token);
+ },
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ let bids = [];
+ let bidIds = [];
+ validBidRequests.forEach(bidRequest => {
+ let sizes = prepareSizes(bidRequest.sizes)
+
+ bids.push({
+ token: bidRequest.params.token,
+ pos: bidRequest.params.pos,
+ bidFloor: bidRequest.params.bidFloor,
+ width: sizes[0].width,
+ height: sizes[0].height,
+ impMediaType: getFormatType(bidRequest)
+ })
+ bidIds.push(bidRequest.bidId)
+ })
+
+ const alkimiConfig = config.getConfig('alkimi');
+
+ let payload = {
+ requestId: bidderRequest.auctionId,
+ signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID },
+ bidIds,
+ referer: bidderRequest.refererInfo.referer,
+ signature: alkimiConfig && alkimiConfig.signature
+ }
+
+ const options = {
+ contentType: 'application/json',
+ customHeaders: {
+ 'Rtb-Direct': true
+ }
+ }
+
+ return {
+ method: 'POST',
+ url: ENDPOINT,
+ data: payload,
+ options
+ };
+ },
+
+ interpretResponse: function (serverResponse, request) {
+ const serverBody = serverResponse.body;
+ if (!serverBody || typeof serverBody !== 'object') {
+ return [];
+ }
+
+ const { prebidResponse } = serverBody;
+ if (!prebidResponse || typeof prebidResponse !== 'object') {
+ return [];
+ }
+
+ let bids = [];
+ prebidResponse.forEach(bidResponse => {
+ let bid = deepClone(bidResponse);
+ bid.cpm = parseFloat(bidResponse.cpm);
+
+ // banner or video
+ if (VIDEO === bid.mediaType) {
+ bid.vastXml = bid.ad;
+ }
+
+ bid.meta = {};
+ bid.meta.advertiserDomains = bid.adomain || [];
+
+ bids.push(bid);
+ })
+
+ return bids;
+ },
+
+ onBidWon: function (bid) {
+ let winUrl;
+ if (bid.winUrl || bid.vastUrl) {
+ winUrl = bid.winUrl ? bid.winUrl : bid.vastUrl;
+ winUrl = winUrl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm);
+ } else if (bid.ad) {
+ let trackImg = bid.ad.match(/(?!^)
/);
+ bid.ad = bid.ad.replace(trackImg[0], '');
+ winUrl = trackImg[0].split('"')[1];
+ winUrl = winUrl.replace(/\$%7BAUCTION_PRICE%7D/, bid.cpm);
+ } else {
+ return false;
+ }
+
+ ajax(winUrl, null);
+ return true;
+ }
+}
+
+function prepareSizes(sizes) {
+ return sizes && sizes.map(size => ({ width: size[0], height: size[1] }));
+}
+
+const getFormatType = bidRequest => {
+ if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner'
+ if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video'
+ if (deepAccess(bidRequest, 'mediaTypes.audio')) return 'Audio'
+}
+
+registerBidder(spec);
diff --git a/modules/alkimiBidAdapter.md b/modules/alkimiBidAdapter.md
new file mode 100644
index 00000000000..2d1fd42c70f
--- /dev/null
+++ b/modules/alkimiBidAdapter.md
@@ -0,0 +1,35 @@
+# Overview
+
+```
+Module Name: Alkimi Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: kalidas@alkimiexchange.com
+```
+
+# Description
+
+Connects to Alkimi Bidder for bids.
+Alkimi bid adapter supports Banner and Video ads.
+
+# Test Parameters
+```
+const adUnits = [
+ {
+ code: 'banner1',
+ mediaTypes: {
+ banner: { // Media Type can be banner or video or ...
+ sizes: [[300, 250]],
+ }
+ },
+ bids: [
+ {
+ bidder: 'alkimi',
+ params: {
+ bidFloor: 0.5,
+ token: 'a6b042a5-2d68-4170-a051-77fbaf00203a', // Publisher Token(Id) provided by Alkimi
+ }
+ }
+ ]
+ }
+]
+```
diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js
index d48245e9604..d1754936d7f 100644
--- a/modules/amxBidAdapter.js
+++ b/modules/amxBidAdapter.js
@@ -5,7 +5,7 @@ import { config } from '../src/config.js';
import { getStorageManager } from '../src/storageManager.js';
const BIDDER_CODE = 'amx';
-const storage = getStorageManager(737, BIDDER_CODE);
+const storage = getStorageManager({gvlid: 737, bidderCode: BIDDER_CODE});
const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/;
const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c';
const VERSION = 'pba1.3.1';
diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js
index 53249e92a77..7760aa2b47b 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', 'didnavideo'],
+ aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo', 'ottadvisors'],
supportedMediaTypes: [VIDEO, BANNER],
isBidRequestValid,
buildRequests,
diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js
index 421eb99b4c1..d7b6b7c4020 100644
--- a/modules/apacdexBidAdapter.js
+++ b/modules/apacdexBidAdapter.js
@@ -2,22 +2,9 @@ import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn } from '.
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
const BIDDER_CODE = 'apacdex';
-const CONFIG = {
- 'apacdex': {
- 'ENDPOINT': 'https://useast.quantumdex.io/auction/apacdex',
- 'USERSYNC': 'https://sync.quantumdex.io/usersync/apacdex'
- },
- 'quantumdex': {
- 'ENDPOINT': 'https://useast.quantumdex.io/auction/quantumdex',
- 'USERSYNC': 'https://sync.quantumdex.io/usersync/quantumdex'
- },
- 'valueimpression': {
- 'ENDPOINT': 'https://useast.quantumdex.io/auction/adapter',
- 'USERSYNC': 'https://sync.quantumdex.io/usersync/adapter'
- }
-};
+const ENDPOINT = 'https://useast.quantumdex.io/auction/pbjs'
+const USERSYNC = 'https://sync.quantumdex.io/usersync/pbjs'
-var bidderConfig = CONFIG[BIDDER_CODE];
var bySlotTargetKey = {};
var bySlotSizesCount = {}
@@ -56,8 +43,6 @@ export const spec = {
let test;
let bids = [];
- bidderConfig = CONFIG[validBidRequests[0].bidder];
-
test = config.getConfig('debug');
validBidRequests.forEach(bidReq => {
@@ -156,13 +141,14 @@ export const spec = {
transactionId: bid.transactionId,
sizes: bid.sizes,
bidId: bid.bidId,
+ adUnitCode: bid.adUnitCode,
bidFloor: bid.bidFloor
}
});
return {
method: 'POST',
- url: bidderConfig.ENDPOINT,
+ url: ENDPOINT,
data: payload,
withCredentials: true,
bidderRequests: bids
@@ -209,32 +195,47 @@ export const spec = {
});
return bidResponses;
},
- getUserSyncs: function (syncOptions, serverResponses) {
+ getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
const syncs = [];
- try {
- if (syncOptions.iframeEnabled) {
- syncs.push({
- type: 'iframe',
- url: bidderConfig.USERSYNC
- });
+ if (hasPurpose1Consent(gdprConsent)) {
+ let params = '';
+ if (gdprConsent && typeof gdprConsent.consentString === 'string') {
+ // add 'gdpr' only if 'gdprApplies' is defined
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ params = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
+ } else {
+ params = `?gdpr_consent=${gdprConsent.consentString}`;
+ }
}
- if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) {
- serverResponses[0].body.pixel.forEach(px => {
- if (px.type === 'image' && syncOptions.pixelEnabled) {
- syncs.push({
- type: 'image',
- url: px.url
- });
- }
- if (px.type === 'iframe' && syncOptions.iframeEnabled) {
- syncs.push({
- type: 'iframe',
- url: px.url
- });
- }
- });
+ if (uspConsent) {
+ params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`;
}
- } catch (e) { }
+
+ try {
+ if (syncOptions.iframeEnabled) {
+ syncs.push({
+ type: 'iframe',
+ url: USERSYNC + params
+ });
+ }
+ if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) {
+ serverResponses[0].body.pixel.forEach(px => {
+ if (px.type === 'image' && syncOptions.pixelEnabled) {
+ syncs.push({
+ type: 'image',
+ url: px.url + params
+ });
+ }
+ if (px.type === 'iframe' && syncOptions.iframeEnabled) {
+ syncs.push({
+ type: 'iframe',
+ url: px.url + params
+ });
+ }
+ });
+ }
+ } catch (e) { }
+ }
return syncs;
}
};
@@ -377,4 +378,14 @@ function getBidFloor(bid) {
return null;
}
+function hasPurpose1Consent(gdprConsent) {
+ let result = true;
+ if (gdprConsent) {
+ if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) {
+ result = !!(deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true);
+ }
+ }
+ return result;
+}
+
registerBidder(spec);
diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js
index 1829323df2a..aa5b604781d 100644
--- a/modules/appnexusBidAdapter.js
+++ b/modules/appnexusBidAdapter.js
@@ -1,14 +1,38 @@
-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 { Renderer } from '../src/Renderer.js';
-import { config } from '../src/config.js';
-import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js';
-import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js';
-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 { OUTSTREAM, INSTREAM } from '../src/video.js';
-import { getStorageManager } from '../src/storageManager.js';
-import { bidderSettings } from '../src/bidderSettings.js';
+import {
+ chunk,
+ convertCamelToUnderscore,
+ convertTypes,
+ createTrackPixelHtml,
+ deepAccess,
+ deepClone,
+ fill,
+ getBidRequest,
+ getMaxValueFromArray,
+ getMinValueFromArray,
+ getParameterByName,
+ isArray,
+ isArrayOfNums,
+ isEmpty,
+ isFn,
+ isNumber,
+ isPlainObject,
+ isStr,
+ logError,
+ logInfo,
+ logMessage,
+ logWarn,
+ transformBidderParamKeywords,
+ getWindowFromDocument
+} from '../src/utils.js';
+import {Renderer} from '../src/Renderer.js';
+import {config} from '../src/config.js';
+import {getIabSubCategory, registerBidder} from '../src/adapters/bidderFactory.js';
+import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {auctionManager} from '../src/auctionManager.js';
+import {find, includes} from '../src/polyfill.js';
+import {INSTREAM, OUTSTREAM} from '../src/video.js';
+import {getStorageManager} from '../src/storageManager.js';
+import {bidderSettings} from '../src/bidderSettings.js';
const BIDDER_CODE = 'appnexus';
const URL = 'https://ib.adnxs.com/ut/v3/prebid';
@@ -61,7 +85,7 @@ const SCRIPT_TAG_START = '
`;
diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js
index 6223626834d..a798671cbaf 100644
--- a/modules/bizzclickBidAdapter.js
+++ b/modules/bizzclickBidAdapter.js
@@ -95,6 +95,7 @@ export const spec = {
},
regs: {
coppa: config.getConfig('coppa') === true ? 1 : 0,
+ ext: {}
},
user: {
ext: {}
@@ -106,25 +107,15 @@ export const spec = {
imp: [impObject],
};
- if (bidderRequest && bidderRequest.uspConsent) {
- data.regs.ext.us_privacy = bidderRequest.uspConsent;
- }
-
- if (bidderRequest && bidderRequest.gdprConsent) {
- let { gdprApplies, consentString } = bidderRequest.gdprConsent;
- data.regs.ext.gdpr = gdprApplies ? 1 : 0;
- data.user.ext.consent = consentString;
- }
-
- if (bidRequest.schain) {
- deepSetValue(data, 'source.ext.schain', bidRequest.schain);
- }
-
let connection = navigator.connection || navigator.webkitConnection;
if (connection && connection.effectiveType) {
data.device.connectiontype = connection.effectiveType;
}
if (bidRequest) {
+ if (bidRequest.schain) {
+ deepSetValue(data, 'source.ext.schain', bidRequest.schain);
+ }
+
if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) {
deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0);
deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString);
diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js
index 03fb0b92c8f..d362dfa5fdb 100644
--- a/modules/bluebillywigBidAdapter.js
+++ b/modules/bluebillywigBidAdapter.js
@@ -1,10 +1,10 @@
-import { deepAccess, deepSetValue, deepClone, logWarn, logError } from '../src/utils.js';
-import find from 'core-js-pure/features/array/find.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { VIDEO } from '../src/mediaTypes.js';
-import { config } from '../src/config.js';
-import { Renderer } from '../src/Renderer.js';
-import { createEidsArray } from './userId/eids.js';
+import {deepAccess, deepClone, deepSetValue, logError, logWarn} from '../src/utils.js';
+import {find} from '../src/polyfill.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
+import {Renderer} from '../src/Renderer.js';
+import {createEidsArray} from './userId/eids.js';
const DEV_MODE = window.location.search.match(/bbpbs_debug=true/);
diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js
index 5d545b6f722..b141763af8e 100644
--- a/modules/bridgewellBidAdapter.js
+++ b/modules/bridgewellBidAdapter.js
@@ -1,7 +1,7 @@
-import { _each, inIframe, deepSetValue } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, NATIVE } from '../src/mediaTypes.js';
-import find from 'core-js-pure/features/array/find.js';
+import {_each, deepSetValue, inIframe} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, NATIVE} from '../src/mediaTypes.js';
+import {find} from '../src/polyfill.js';
const BIDDER_CODE = 'bridgewell';
const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb=';
diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js
index a1943afda8d..15f2d58010d 100644
--- a/modules/browsiRtdProvider.js
+++ b/modules/browsiRtdProvider.js
@@ -15,14 +15,15 @@
* @property {?string} keyName
*/
-import { deepClone, logError, isGptPubadsDefined, isNumber, isFn, deepSetValue } from '../src/utils.js';
+import {deepClone, deepSetValue, isFn, isGptPubadsDefined, isNumber, logError, logInfo, generateUUID} 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 {find, includes} from '../src/polyfill.js';
import {getGlobal} from '../src/prebidGlobal.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import * as events from '../src/events.js';
+import CONSTANTS from '../src/constants.json';
const storage = getStorageManager();
@@ -107,6 +108,7 @@ export function setData(data) {
}
function getRTD(auc) {
+ logInfo(`Browsi RTD provider is fetching data for ${auc}`);
try {
const _bp = (_browsiData && _browsiData.p) || {};
return auc.reduce((rp, uc) => {
@@ -332,13 +334,23 @@ export const browsiSubmodule = {
getBidRequestData: setBidRequestsData
};
-function getTargetingData(uc) {
+function getTargetingData(uc, c, us, a) {
const targetingData = getRTD(uc);
+ const auctionId = a.auctionId
uc.forEach(auc => {
if (isNumber(_ic[auc])) {
_ic[auc] = _ic[auc] + 1;
}
+ const transactionId = a.adUnits.find(adUnit => adUnit.code === auc).transactionId;
+ events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, {
+ vendor: 'browsi',
+ type: 'adRequest',
+ billingId: generateUUID(),
+ transactionId: transactionId,
+ auctionId: auctionId
+ })
});
+ logInfo('Browsi RTD provider returned targeting data', targetingData, 'for', uc)
return targetingData;
}
diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js
index 38bc99f1d83..65d1ced30e2 100644
--- a/modules/ccxBidAdapter.js
+++ b/modules/ccxBidAdapter.js
@@ -3,8 +3,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'
import { config } from '../src/config.js'
import { getStorageManager } from '../src/storageManager.js';
-const storage = getStorageManager();
const BIDDER_CODE = 'ccx'
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid'
const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6]
const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv']
diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js
index 3c2d3c51bf5..3fda9917715 100644
--- a/modules/cleanmedianetBidAdapter.js
+++ b/modules/cleanmedianetBidAdapter.js
@@ -1,9 +1,9 @@
-import { getDNT, inIframe, isArray, isNumber, logError, deepAccess, logWarn } from '../src/utils.js';
+import {deepAccess, getDNT, inIframe, isArray, isNumber, logError, logWarn} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {config} from '../src/config.js';
import {Renderer} from '../src/Renderer.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {includes} from '../src/polyfill.js';
export const helper = {
getTopWindowDomain: function (url) {
diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js
index e3d3c65a4f0..f61d58664ca 100644
--- a/modules/cointrafficBidAdapter.js
+++ b/modules/cointrafficBidAdapter.js
@@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js'
import { config } from '../src/config.js'
const BIDDER_CODE = 'cointraffic';
-const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp';
+const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp';
const DEFAULT_CURRENCY = 'EUR';
const ALLOWED_CURRENCIES = [
'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY',
diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js
index 72df7c7b465..fec0d1b6510 100644
--- a/modules/colossussspBidAdapter.js
+++ b/modules/colossussspBidAdapter.js
@@ -2,10 +2,11 @@ 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';
+import { config } from '../src/config.js';
const BIDDER_CODE = 'colossusssp';
const G_URL = 'https://colossusssp.com/?c=o&m=multi';
-const G_URL_SYNC = 'https://colossusssp.com/?c=o&m=cookie';
+const G_URL_SYNC = 'https://sync.colossusssp.com';
function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) {
@@ -60,12 +61,33 @@ export const spec = {
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: (validBidRequests, bidderRequest) => {
- const winTop = getWindowTop();
- const location = winTop.location;
+ let deviceWidth = 0;
+ let deviceHeight = 0;
+ let winLocation;
+
+ try {
+ const winTop = getWindowTop();
+ 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);
+ }
+
+ const location = refferLocation || winLocation;
let placements = [];
let request = {
- deviceWidth: winTop.screen.width,
- deviceHeight: winTop.screen.height,
+ deviceWidth,
+ deviceHeight,
language: (navigator && navigator.language) ? navigator.language : '',
secure: location.protocol === 'https:' ? 1 : 0,
host: location.host,
@@ -90,7 +112,6 @@ export const spec = {
placementId: bid.params.placement_id,
groupId: bid.params.group_id,
bidId: bid.bidId,
- sizes: bid.mediaTypes[traff].sizes,
traffic: traff,
eids: [],
floor: {}
@@ -124,7 +145,12 @@ export const spec = {
rtiPartner: 'TDID'
});
}
+ if (traff === BANNER) {
+ placement.sizes = bid.mediaTypes[BANNER].sizes
+ }
+
if (traff === VIDEO) {
+ placement.sizes = bid.mediaTypes[VIDEO].playerSize;
placement.playerSize = bid.mediaTypes[VIDEO].playerSize;
placement.minduration = bid.mediaTypes[VIDEO].minduration;
placement.maxduration = bid.mediaTypes[VIDEO].maxduration;
@@ -175,10 +201,26 @@ export const spec = {
return response;
},
- getUserSyncs: () => {
+ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
+ let syncType = syncOptions.iframeEnabled ? 'html' : 'hms.gif';
+ let syncUrl = G_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}`;
+ }
+ }
+ if (uspConsent && uspConsent.consentString) {
+ syncUrl += `&ccpa_consent=${uspConsent.consentString}`;
+ }
+
+ const coppa = config.getConfig('coppa') ? 1 : 0;
+ syncUrl += `&coppa=${coppa}`;
+
return [{
- type: 'image',
- url: G_URL_SYNC
+ type: syncType,
+ url: syncUrl
}];
},
diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js
index 9a55e9cef1d..99e2492fb94 100644
--- a/modules/concertBidAdapter.js
+++ b/modules/concertBidAdapter.js
@@ -166,7 +166,7 @@ export const spec = {
registerBidder(spec);
-const storage = getStorageManager();
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
/**
* Check or generate a UID for the current user.
diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js
index ac44a8b5a2d..2da2eda4c77 100644
--- a/modules/connectIdSystem.js
+++ b/modules/connectIdSystem.js
@@ -7,8 +7,8 @@
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';
+import {formatQS, logError} from '../src/utils.js';
+import {includes} from '../src/polyfill.js';
const MODULE_NAME = 'connectId';
const VENDOR_ID = 25;
diff --git a/modules/consentManagement.js b/modules/consentManagement.js
index ecd0c0eec4b..5fbcc0f8ac1 100644
--- a/modules/consentManagement.js
+++ b/modules/consentManagement.js
@@ -1,15 +1,13 @@
-
/**
* This module adds GDPR consentManagement support to prebid.js. It interacts with
* supported CMPs (Consent Management Platforms) to grab the user's consent information
* and make it available for any GDPR supported adapters to read/pass this information to
* their system.
*/
-import { logInfo, isFn, getAdUnitSizes, logWarn, isStr, isPlainObject, logError, isNumber } from '../src/utils.js';
-import { config } from '../src/config.js';
-import { gdprDataHandler } from '../src/adapterManager.js';
-import includes from 'core-js-pure/features/array/includes.js';
-import strIncludes from 'core-js-pure/features/string/includes.js';
+import {getAdUnitSizes, isFn, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js';
+import {config} from '../src/config.js';
+import {gdprDataHandler} from '../src/adapterManager.js';
+import {includes} from '../src/polyfill.js';
const DEFAULT_CMP = 'iab';
const DEFAULT_CONSENT_TIMEOUT = 10000;
@@ -36,23 +34,22 @@ const cmpCallMap = {
/**
* This function reads the consent string from the config to obtain the consent information of the user.
- * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP
- * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string)
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
+ * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP
*/
-function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) {
- cmpSuccess(staticConsentData, hookConfig);
+function lookupStaticConsentData({onSuccess, onError}) {
+ processCmpData(staticConsentData, {onSuccess, onError})
}
/**
* This function handles interacting with an IAB compliant CMP to obtain the consent information of the user.
* Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function
* based on the appropriate result.
- * @param {function(string)} cmpSuccess acts as a success callback when CMP returns a value; pass along consentObject (string) from CMP
- * @param {function(string)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string)
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
+ * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP
+ * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging)
+ * @param width
+ * @param height size info passed to the SafeFrame API (used only for TCFv1 when Prebid is running within a safeframe)
*/
-function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
+function lookupIabConsent({onSuccess, onError, width, height}) {
function findCMP() {
let f = window;
let cmpFrame;
@@ -102,10 +99,10 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
logInfo('Received a response from CMP', tcfData);
if (success) {
if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') {
- cmpSuccess(tcfData, hookConfig);
+ processCmpData(tcfData, {onSuccess, onError});
}
} else {
- cmpError('CMP unable to register callback function. Please check CMP setup.', hookConfig);
+ onError('CMP unable to register callback function. Please check CMP setup.');
}
}
@@ -115,7 +112,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
function afterEach() {
if (cmpResponse.getConsentData && cmpResponse.getVendorConsents) {
logInfo('Received all requested responses from CMP', cmpResponse);
- cmpSuccess(cmpResponse, hookConfig);
+ processCmpData(cmpResponse, {onSuccess, onError});
}
}
@@ -136,7 +133,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
let { cmpFrame, cmpFunction } = findCMP();
if (!cmpFrame) {
- return cmpError('CMP not found.', hookConfig);
+ return onError('CMP not found.');
}
// to collect the consent information from the user, we perform two calls to the CMP in parallel:
// first to collect the user's consent choices represented in an encoded string (via getConsentData)
@@ -183,16 +180,6 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
}
}
- // find sizes from adUnits object
- let adUnits = hookConfig.adUnits;
- let width = 1;
- let height = 1;
- if (Array.isArray(adUnits) && adUnits.length > 0) {
- let sizes = getAdUnitSizes(adUnits[0]);
- width = sizes[0][0];
- height = sizes[0][1];
- }
-
window.$sf.ext.register(width, height, sfCallback);
window.$sf.ext.cmp(commandName);
}
@@ -249,7 +236,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
function readPostMessageResponse(event) {
let cmpDataPkgName = `${apiName}Return`;
- let json = (typeof event.data === 'string' && strIncludes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data;
+ let json = (typeof event.data === 'string' && includes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data;
if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) {
let payload = json[cmpDataPkgName];
// TODO - clean up this logic (move listeners?); we have duplicate messages responses because 2 eventlisteners are active from the 2 cmp requests running in parallel
@@ -261,6 +248,70 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
}
}
+/**
+ * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler.
+ *
+ * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra
+ * error arguments that will be undefined if there's no error.
+ * @param width if we are running in an iframe, the TCFv1 spec requires us to use the SafeFrame API to find the CMP - which
+ * in turn requires width and height.
+ * @param height see width above
+ */
+function loadConsentData(cb, width = 1, height = 1) {
+ let isDone = false;
+ let timer = null;
+
+ function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) {
+ if (timer != null) {
+ clearTimeout(timer);
+ }
+ isDone = true;
+ gdprDataHandler.setConsentData(consentData);
+ if (cb != null) {
+ cb(shouldCancelAuction, errMsg, ...extraArgs);
+ }
+ }
+
+ if (!includes(Object.keys(cmpCallMap), userCMP)) {
+ done(null, false, `CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`);
+ return;
+ }
+
+ const callbacks = {
+ onSuccess: (data) => done(data, false),
+ onError: function (msg, ...extraArgs) {
+ let consentData = null;
+ let shouldCancelAuction = true;
+ if (allowAuction.value && cmpVersion === 1) {
+ // still set the consentData to undefined when there is a problem as per config options
+ consentData = storeConsentData(undefined);
+ shouldCancelAuction = false;
+ }
+ done(consentData, shouldCancelAuction, msg, ...extraArgs);
+ }
+ }
+ cmpCallMap[userCMP]({
+ width,
+ height,
+ ...callbacks
+ });
+
+ if (!isDone) {
+ if (consentTimeout === 0) {
+ processCmpData(undefined, callbacks);
+ } else {
+ timer = setTimeout(function () {
+ if (cmpVersion === 2) {
+ // for TCFv2, we allow the auction to continue on timeout
+ done(storeConsentData(undefined), false, `No response from CMP, continuing auction...`)
+ } else {
+ callbacks.onError('CMP workflow exceeded timeout threshold.');
+ }
+ }, consentTimeout);
+ }
+ }
+}
+
/**
* If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the
* user's encoded consent string from the supported CMP. Once obtained, the module will store this
@@ -270,48 +321,60 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
* @param {function} fn required; The next function in the chain, used by hook.js
*/
export function requestBidsHook(fn, reqBidsConfigObj) {
- // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions)
- const hookConfig = {
- context: this,
- args: [reqBidsConfigObj],
- nextFn: fn,
- adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits,
- bidsBackHandler: reqBidsConfigObj.bidsBackHandler,
- haveExited: false,
- timer: null
- };
-
- // in case we already have consent (eg during bid refresh)
- if (consentData) {
- logInfo('User consent information already known. Pulling internally stored information...');
- return exitModule(null, hookConfig);
- }
+ const load = (() => {
+ if (consentData) {
+ logInfo('User consent information already known. Pulling internally stored information...');
+ return function (cb) {
+ // eslint-disable-next-line standard/no-callback-literal
+ cb(false);
+ }
+ } else {
+ // find sizes from adUnits object
+ let adUnits = reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits;
+ let width = 1;
+ let height = 1;
+ if (Array.isArray(adUnits) && adUnits.length > 0) {
+ let sizes = getAdUnitSizes(adUnits[0]);
+ width = sizes?.[0]?.[0] || 1;
+ height = sizes?.[0]?.[1] || 1;
+ }
- if (!includes(Object.keys(cmpCallMap), userCMP)) {
- logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`);
- return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args);
- }
+ return function (cb) {
+ loadConsentData(cb, width, height);
+ }
+ }
+ })();
- cmpCallMap[userCMP].call(this, processCmpData, cmpFailed, hookConfig);
+ load(function (shouldCancelAuction, errMsg, ...extraArgs) {
+ if (errMsg) {
+ let log = logWarn;
+ if (cmpVersion === 1 && !shouldCancelAuction) {
+ errMsg = `${errMsg} 'allowAuctionWithoutConsent' activated.`;
+ } else if (shouldCancelAuction) {
+ log = logError;
+ errMsg = `${errMsg} Canceling auction as per consentManagement config.`;
+ }
+ log(errMsg, ...extraArgs);
+ }
- // only let this code run if module is still active (ie if the callbacks used by CMPs haven't already finished)
- if (!hookConfig.haveExited) {
- if (consentTimeout === 0) {
- processCmpData(undefined, hookConfig);
+ if (shouldCancelAuction) {
+ if (typeof reqBidsConfigObj.bidsBackHandler === 'function') {
+ reqBidsConfigObj.bidsBackHandler();
+ } else {
+ logError('Error executing bidsBackHandler');
+ }
} else {
- hookConfig.timer = setTimeout(cmpTimedOut.bind(null, hookConfig), consentTimeout);
+ fn.call(this, reqBidsConfigObj);
}
- }
+ });
}
/**
* This function checks the consent data provided by CMP to ensure it's in an expected state.
- * If it's bad, we exit the module depending on config settings.
- * If it's good, then we store the value and exits the module.
- * @param {object} consentObject required; object returned by CMP that contains user's consent choices
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
+ * If it's bad, we call `onError`
+ * If it's good, then we store the value and call `onSuccess`
*/
-function processCmpData(consentObject, hookConfig) {
+function processCmpData(consentObject, {onSuccess, onError}) {
function checkV1Data(consentObject) {
let gdprApplies = consentObject && consentObject.getConsentData && consentObject.getConsentData.gdprApplies;
return !!(
@@ -347,57 +410,19 @@ function processCmpData(consentObject, hookConfig) {
// determine which set of checks to run based on cmpVersion
let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null;
- // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2.
- if (allowAuction.definedInConfig && cmpVersion === 2) {
- logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`);
- } else if (!allowAuction.definedInConfig && cmpVersion === 1) {
- logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`);
- }
-
if (isFn(checkFn)) {
if (checkFn(consentObject)) {
- cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject);
+ onError(`CMP returned unexpected value during lookup process.`, consentObject);
} else {
- clearTimeout(hookConfig.timer);
- storeConsentData(consentObject);
- exitModule(null, hookConfig);
+ onSuccess(storeConsentData(consentObject));
}
} else {
- cmpFailed('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', hookConfig, consentObject);
+ onError('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', consentObject);
}
}
/**
- * General timeout callback when interacting with CMP takes too long.
- */
-function cmpTimedOut(hookConfig) {
- if (cmpVersion === 2) {
- logWarn(`No response from CMP, continuing auction...`)
- storeConsentData(undefined);
- exitModule(null, hookConfig)
- } else {
- cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig);
- }
-}
-
-/**
- * This function contains the controlled steps to perform when there's a problem with CMP.
- * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened.
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
- * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging
-*/
-function cmpFailed(errMsg, hookConfig, extraArgs) {
- clearTimeout(hookConfig.timer);
-
- // still set the consentData to undefined when there is a problem as per config options
- if (allowAuction.value && cmpVersion === 1) {
- storeConsentData(undefined);
- }
- exitModule(errMsg, hookConfig, extraArgs);
-}
-
-/**
- * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction
+ * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction
* @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only)
*/
function storeConsentData(cmpConsentObject) {
@@ -418,50 +443,7 @@ function storeConsentData(cmpConsentObject) {
};
}
consentData.apiVersion = cmpVersion;
- gdprDataHandler.setConsentData(consentData);
-}
-
-/**
- * This function handles the exit logic for the module.
- * While there are several paths in the module's logic to call this function, we only allow 1 of the 3 potential exits to happen before suppressing others.
- *
- * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios.
- * One scenario could be auction was canceled due to timeout with CMP being reached.
- * While the timeout is the accepted exit and runs first, the CMP's callback still tries to process the user's data (which normally leads to a good exit).
- * In this case, the good exit will be suppressed since we already decided to cancel the auction.
- *
- * Three exit paths are:
- * 1. good exit where auction runs (CMP data is processed normally).
- * 2. bad exit but auction still continues (warning message is logged, CMP data is undefined and still passed along).
- * 3. bad exit with auction canceled (error message is logged).
- * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered.
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
- * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging
- */
-function exitModule(errMsg, hookConfig, extraArgs) {
- if (hookConfig.haveExited === false) {
- hookConfig.haveExited = true;
-
- let context = hookConfig.context;
- let args = hookConfig.args;
- let nextFn = hookConfig.nextFn;
-
- if (errMsg) {
- if (allowAuction.value && cmpVersion === 1) {
- logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs);
- nextFn.apply(context, args);
- } else {
- logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs);
- if (typeof hookConfig.bidsBackHandler === 'function') {
- hookConfig.bidsBackHandler();
- } else {
- logError('Error executing bidsBackHandler');
- }
- }
- } else {
- nextFn.apply(context, args);
- }
- }
+ return consentData;
}
/**
@@ -471,7 +453,7 @@ export function resetConsentData() {
consentData = undefined;
userCMP = undefined;
cmpVersion = 0;
- gdprDataHandler.setConsentData(null);
+ gdprDataHandler.reset();
}
/**
@@ -522,5 +504,14 @@ export function setConsentConfig(config) {
$$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50);
}
addedConsentHook = true;
+ gdprDataHandler.enable();
+ loadConsentData(); // immediately look up consent data to make it available without requiring an auction
+
+ // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2.
+ if (allowAuction.definedInConfig && cmpVersion === 2) {
+ logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`);
+ } else if (!allowAuction.definedInConfig && cmpVersion === 1) {
+ logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`);
+ }
}
config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement));
diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js
index 4a4c4ae0a55..d6bf913b366 100644
--- a/modules/consentManagementUsp.js
+++ b/modules/consentManagementUsp.js
@@ -27,23 +27,17 @@ const uspCallMap = {
/**
* This function reads the consent string from the config to obtain the consent information of the user.
- * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP
- * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string)
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
*/
-function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) {
- cmpSuccess(staticConsentData, hookConfig);
+function lookupStaticConsentData({onSuccess, onError}) {
+ processUspData(staticConsentData, {onSuccess, onError});
}
/**
* This function handles interacting with an USP compliant consent manager to obtain the consent information of the user.
* Given the async nature of the USP's API, we pass in acting success/error callback functions to exit this function
* based on the appropriate result.
- * @param {function(string)} uspSuccess acts as a success callback when USPAPI returns a value; pass along consentObject (string) from USPAPI
- * @param {function(string)} uspError acts as an error callback while interacting with USPAPI; pass along an error message (string)
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
*/
-function lookupUspConsent(uspSuccess, uspError, hookConfig) {
+function lookupUspConsent({onSuccess, onError}) {
function findUsp() {
let f = window;
let uspapiFrame;
@@ -78,9 +72,9 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) {
function afterEach() {
if (uspResponse.usPrivacy) {
- uspSuccess(uspResponse, hookConfig);
+ processUspData(uspResponse, {onSuccess, onError})
} else {
- uspError('Unable to get USP consent string.', hookConfig);
+ onError('Unable to get USP consent string.');
}
}
@@ -100,7 +94,7 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) {
let { uspapiFrame, uspapiFunction } = findUsp();
if (!uspapiFrame) {
- return uspError('USP CMP not found.', hookConfig);
+ return onError('USP CMP not found.');
}
// to collect the consent information from the user, we perform a call to USPAPI
@@ -165,119 +159,92 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) {
}
/**
- * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the
- * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this
- * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler object.
- * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system.
- * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids.
- * @param {function} fn required; The next function in the chain, used by hook.js
+ * Lookup consent data and store it in the `consentData` global as well as `adapterManager.js`' uspDataHanlder.
+ *
+ * @param cb a callback that takes an error message and extra error arguments; all args will be undefined if consent
+ * data was retrieved successfully.
*/
-export function requestBidsHook(fn, reqBidsConfigObj) {
- // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions)
- const hookConfig = {
- context: this,
- args: [reqBidsConfigObj],
- nextFn: fn,
- adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits,
- bidsBackHandler: reqBidsConfigObj.bidsBackHandler,
- haveExited: false,
- timer: null
- };
+function loadConsentData(cb) {
+ let timer = null;
+ let isDone = false;
+
+ function done(consentData, errMsg, ...extraArgs) {
+ if (timer != null) {
+ clearTimeout(timer);
+ }
+ isDone = true;
+ uspDataHandler.setConsentData(consentData);
+ if (cb != null) {
+ cb(errMsg, ...extraArgs)
+ }
+ }
if (!uspCallMap[consentAPI]) {
- logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`);
- return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args);
+ done(null, `USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`);
+ return;
+ }
+
+ const callbacks = {
+ onSuccess: done,
+ onError: function (errMsg, ...extraArgs) {
+ done(null, `${errMsg} Resuming auction without consent data as per consentManagement config.`, ...extraArgs);
+ }
}
- uspCallMap[consentAPI].call(this, processUspData, uspapiFailed, hookConfig);
+ uspCallMap[consentAPI](callbacks);
- // only let this code run if module is still active (ie if the callbacks used by USPs haven't already finished)
- if (!hookConfig.haveExited) {
+ if (!isDone) {
if (consentTimeout === 0) {
- processUspData(undefined, hookConfig);
+ processUspData(undefined, callbacks);
} else {
- hookConfig.timer = setTimeout(uspapiTimeout.bind(null, hookConfig), consentTimeout);
+ timer = setTimeout(callbacks.onError.bind(null, 'USPAPI workflow exceeded timeout threshold.'), consentTimeout)
}
}
}
+/**
+ * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the
+ * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this
+ * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler object.
+ * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system.
+ * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids.
+ * @param {function} fn required; The next function in the chain, used by hook.js
+ */
+export function requestBidsHook(fn, reqBidsConfigObj) {
+ loadConsentData((errMsg, ...extraArgs) => {
+ if (errMsg != null) {
+ logWarn(errMsg, ...extraArgs);
+ }
+ fn.call(this, reqBidsConfigObj);
+ });
+}
+
/**
* This function checks the consent data provided by USPAPI to ensure it's in an expected state.
* If it's bad, we exit the module depending on config settings.
* If it's good, then we store the value and exits the module.
* @param {object} consentObject required; object returned by USPAPI that contains user's consent choices
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
+ * @param {function(string)} onSuccess callback accepting the resolved consent USP consent string
+ * @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging)
*/
-function processUspData(consentObject, hookConfig) {
+function processUspData(consentObject, {onSuccess, onError}) {
const valid = !!(consentObject && consentObject.usPrivacy);
if (!valid) {
- uspapiFailed(`USPAPI returned unexpected value during lookup process.`, hookConfig, consentObject);
+ onError(`USPAPI returned unexpected value during lookup process.`, consentObject);
return;
}
- clearTimeout(hookConfig.timer);
storeUspConsentData(consentObject);
- exitModule(null, hookConfig);
-}
-
-/**
- * General timeout callback when interacting with USPAPI takes too long.
- */
-function uspapiTimeout(hookConfig) {
- uspapiFailed('USPAPI workflow exceeded timeout threshold.', hookConfig);
-}
-
-/**
- * This function contains the controlled steps to perform when there's a problem with USPAPI.
- * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened.
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
- * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging
-*/
-function uspapiFailed(errMsg, hookConfig, extraArgs) {
- clearTimeout(hookConfig.timer);
-
- exitModule(errMsg, hookConfig, extraArgs);
+ onSuccess(consentData);
}
/**
* Stores USP data locally in module and then invokes uspDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction
- * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only)
+ * @param {object} consentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only)
*/
function storeUspConsentData(consentObject) {
if (consentObject && consentObject.usPrivacy) {
consentData = consentObject.usPrivacy;
- uspDataHandler.setConsentData(consentData);
- }
-}
-
-/**
- * This function handles the exit logic for the module.
- * There are a couple paths in the module's logic to call this function and we only allow 1 of the 2 potential exits to happen before suppressing others.
- *
- * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios.
- * One scenario could be auction was canceled due to timeout with USPAPI being reached.
- * While the timeout is the accepted exit and runs first, the USP's callback still tries to process the user's data (which normally leads to a good exit).
- * In this case, the good exit will be suppressed since we already decided to cancel the auction.
- *
- * Three exit paths are:
- * 1. good exit where auction runs (USPAPI data is processed normally).
- * 2. bad exit but auction still continues (warning message is logged, USPAPI data is undefined and still passed along).
- * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered.
- * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function)
- * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging
- */
-function exitModule(errMsg, hookConfig, extraArgs) {
- if (hookConfig.haveExited === false) {
- hookConfig.haveExited = true;
-
- let context = hookConfig.context;
- let args = hookConfig.args;
- let nextFn = hookConfig.nextFn;
-
- if (errMsg) {
- logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs);
- }
- nextFn.apply(context, args);
}
}
@@ -287,7 +254,7 @@ function exitModule(errMsg, hookConfig, extraArgs) {
export function resetConsentData() {
consentData = undefined;
consentAPI = undefined;
- uspDataHandler.setConsentData(null);
+ uspDataHandler.reset();
}
/**
@@ -328,5 +295,7 @@ export function setConsentConfig(config) {
$$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50);
}
addedConsentHook = true;
+ uspDataHandler.enable();
+ loadConsentData(); // immediately look up consent data to make it available without requiring an auction
}
config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement));
diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js
index 1a2845ba85b..de08fc8677a 100644
--- a/modules/consumableBidAdapter.js
+++ b/modules/consumableBidAdapter.js
@@ -122,10 +122,30 @@ export const spec = {
bid.currency = 'USD';
bid.creativeId = decision.adId;
bid.ttl = 30;
- bid.meta = { advertiserDomains: decision.adomain ? decision.adomain : [] }
bid.netRevenue = true;
bid.referrer = bidRequest.bidderRequest.refererInfo.referer;
+ bid.meta = {
+ advertiserDomains: decision.adomain || []
+ };
+
+ if (decision.cats) {
+ if (decision.cats.length > 0) {
+ bid.meta.primaryCatId = decision.cats[0];
+ if (decision.cats.length > 1) {
+ bid.meta.secondaryCatIds = decision.cats.slice(1);
+ }
+ }
+ }
+
+ if (decision.networkId) {
+ bid.meta.networkId = decision.networkId;
+ }
+
+ if (decision.mediaType) {
+ bid.meta.mediaType = decision.mediaType;
+ }
+
bidResponses.push(bid);
}
}
@@ -136,13 +156,15 @@ export const spec = {
getUserSyncs: function(syncOptions, serverResponses) {
if (syncOptions.iframeEnabled) {
- return [{
- type: 'iframe',
- url: 'https://sync.serverbid.com/ss/' + siteId + '.html'
- }];
+ if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') {
+ return [{
+ type: 'iframe',
+ url: 'https://sync.serverbid.com/ss/' + siteId + '.html'
+ }];
+ }
}
- if (syncOptions.pixelEnabled && serverResponses.length > 0) {
+ if (syncOptions.pixelEnabled && serverResponses && serverResponses.length > 0) {
return serverResponses[0].body.pixels;
} else {
logWarn(bidder + ': Please enable iframe based user syncing.');
diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js
index d7c3315a814..7ee8b1b7681 100644
--- a/modules/conversantBidAdapter.js
+++ b/modules/conversantBidAdapter.js
@@ -5,9 +5,9 @@ import {getStorageManager} from '../src/storageManager.js';
import { config } from '../src/config.js';
const GVLID = 24;
-export const storage = getStorageManager(GVLID);
const BIDDER_CODE = 'conversant';
+export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25';
export const spec = {
@@ -136,6 +136,12 @@ export const spec = {
let userExt = {};
+ // pass schain object if it is present
+ const schain = deepAccess(validBidRequests, '0.schain');
+ if (schain) {
+ deepSetValue(payload, 'source.ext.schain', schain);
+ }
+
if (bidderRequest) {
// Add GDPR flag and consent string
if (bidderRequest.gdprConsent) {
diff --git a/modules/cpexIdSystem.js b/modules/cpexIdSystem.js
new file mode 100644
index 00000000000..4600601cb11
--- /dev/null
+++ b/modules/cpexIdSystem.js
@@ -0,0 +1,49 @@
+/**
+ * This module adds 'caid' to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/cpexIdSystem
+ * @requires module:modules/userId
+ */
+
+import { submodule } from '../src/hook.js'
+import { getStorageManager } from '../src/storageManager.js'
+
+window.top.cpexIdVersion = '0.0.3'
+
+// Returns StorageManager
+export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' })
+
+// Returns the id string from either cookie or localstorage
+const getId = () => { return storage.getCookie('caid') || storage.getDataFromLocalStorage('caid') }
+
+/** @type {Submodule} */
+export const cpexIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: 'cpexId',
+ /**
+ * Vendor ID of Czech Publisher Exchange
+ * @type {Number}
+ */
+ gvlid: 570,
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function decode
+ * @param {(Object|string)} value
+ * @returns {(Object|undefined)}
+ */
+ decode (value) { return { cpexId: getId() } },
+ /**
+ * performs action to obtain id and return a value in the callback's response argument
+ * @function
+ * @param {SubmoduleConfig} [config]
+ * @param {ConsentData} [consentData]
+ * @param {(Object|undefined)} cacheIdObj
+ * @returns {IdResponse|undefined}
+ */
+ getId (config, consentData) { return { cpexId: getId() } }
+}
+
+submodule('userId', cpexIdSubmodule)
diff --git a/modules/cpexIdSystem.md b/modules/cpexIdSystem.md
new file mode 100644
index 00000000000..8aceb7fe4ec
--- /dev/null
+++ b/modules/cpexIdSystem.md
@@ -0,0 +1,27 @@
+## CPEx User ID Submodule
+
+CPExID is provided by [Czech Publisher Exchange](https://www.cpex.cz/), or CPEx. It is a user ID for ad targeting by using first party cookie, or localStorage mechanism. Please contact CPEx before using this ID.
+
+## Building Prebid with CPExID Support
+
+First, make sure to add the cpexId to your Prebid.js package with:
+
+```
+gulp build --modules=cpexIdSystem
+```
+
+The following configuration parameters are available:
+
+```javascript
+pbjs.setConfig({
+ userSync: {
+ userIds: [{
+ name: 'cpexId'
+ }]
+ }
+});
+```
+
+| Param under userSync.userIds[] | Scope | Type | Description | Example |
+| --- | --- | --- | --- | --- |
+| name | Required | String | The name of this module. | `"cpexId"` |
diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js
index 812ec53d686..61ca4f929e7 100644
--- a/modules/craftBidAdapter.js
+++ b/modules/craftBidAdapter.js
@@ -1,16 +1,24 @@
-import { logError, convertTypes, convertCamelToUnderscore, isArray, deepAccess, getBidRequest, isEmpty, transformBidderParamKeywords } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
-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 {
+ convertCamelToUnderscore,
+ convertTypes,
+ deepAccess,
+ getBidRequest,
+ isArray,
+ isEmpty,
+ logError,
+ transformBidderParamKeywords
+} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {auctionManager} from '../src/auctionManager.js';
+import {find, includes} from '../src/polyfill.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';
const TTL = 360;
-const storage = getStorageManager();
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js
index 0285ab6be5b..2e7cc93cb43 100644
--- a/modules/criteoBidAdapter.js
+++ b/modules/criteoBidAdapter.js
@@ -1,9 +1,9 @@
-import { isArray, getUniqueIdentifierStr, parseUrl, deepAccess, logWarn, logError, logInfo } from '../src/utils.js';
-import {loadExternalScript} from '../src/adloader.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {config} from '../src/config.js';
-import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
-import find from 'core-js-pure/features/array/find.js';
+import { deepAccess, getUniqueIdentifierStr, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js';
+import { loadExternalScript } from '../src/adloader.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { find } from '../src/polyfill.js';
import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2
import { getStorageManager } from '../src/storageManager.js';
@@ -13,7 +13,7 @@ const BIDDER_CODE = 'criteo';
const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb';
const PROFILE_ID_INLINE = 207;
export const PROFILE_ID_PUBLISHERTAG = 185;
-const storage = getStorageManager(GVLID);
+const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE });
const LOG_PREFIX = 'Criteo: ';
/*
@@ -35,7 +35,7 @@ const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDe
export const spec = {
code: BIDDER_CODE,
gvlid: GVLID,
- supportedMediaTypes: [ BANNER, VIDEO, NATIVE ],
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
/** f
* @param {object} bid
@@ -65,11 +65,11 @@ export const spec = {
buildRequests: (bidRequests, bidderRequest) => {
let url;
let data;
- let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {};
+ let fpd = config.getConfig('ortb2') || {};
Object.assign(bidderRequest, {
- publisherExt: fpd.context,
- userExt: fpd.user,
+ publisherExt: fpd.site?.ext,
+ userExt: fpd.user?.ext,
ceh: config.getConfig('criteo.ceh')
});
@@ -139,6 +139,13 @@ export const spec = {
height: slot.height,
dealId: slot.dealCode,
};
+ if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) {
+ const pafResponseMeta = {
+ content_id: slot.ext.paf.content_id,
+ transmission: response.ext.paf.transmission
+ };
+ bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta });
+ }
if (slot.adomain) {
bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain });
}
@@ -265,11 +272,11 @@ function checkNativeSendId(bidRequest) {
return !(bidRequest.nativeParams &&
(
(bidRequest.nativeParams.image && ((bidRequest.nativeParams.image.sendId !== true || bidRequest.nativeParams.image.sendTargetingKeys === true))) ||
- (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) ||
- (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) ||
- (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) ||
- (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) ||
- (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true)))
+ (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) ||
+ (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) ||
+ (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) ||
+ (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) ||
+ (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true)))
));
}
@@ -285,7 +292,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) {
const request = {
publisher: {
url: context.url,
- ext: bidderRequest.publisherExt
+ ext: bidderRequest.publisherExt,
},
slots: bidRequests.map(bidRequest => {
networkId = bidRequest.params.networkId || networkId;
@@ -312,9 +319,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) {
if (!checkNativeSendId(bidRequest)) {
logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)');
}
- slot.sizes = parseSizes(retrieveBannerSizes(bidRequest), parseNativeSize);
+ slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseNativeSize);
} else {
- slot.sizes = parseSizes(retrieveBannerSizes(bidRequest), parseSize);
+ slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize);
}
if (hasVideoMediaType(bidRequest)) {
const video = {
@@ -375,11 +382,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) {
return request;
}
-function retrieveBannerSizes(bidRequest) {
- return deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes;
-}
-
function parseSizes(sizes, parser) {
+ if (sizes == undefined) {
+ return [];
+ }
if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]])
return sizes.map(size => parser(size));
}
@@ -406,7 +412,7 @@ function hasValidVideoMediaType(bidRequest) {
var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod'];
- requiredMediaTypesParams.forEach(function(param) {
+ requiredMediaTypesParams.forEach(function (param) {
if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) {
isValid = false;
logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required');
diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js
index ecf7b3aaac4..c73c4422a77 100644
--- a/modules/criteoIdSystem.js
+++ b/modules/criteoIdSystem.js
@@ -13,7 +13,7 @@ import { getStorageManager } from '../src/storageManager.js';
const gvlid = 91;
const bidderCode = 'criteo';
-export const storage = getStorageManager(gvlid, bidderCode);
+export const storage = getStorageManager({gvlid: gvlid, moduleName: bidderCode});
const bididStorageKey = 'cto_bidid';
const bundleStorageKey = 'cto_bundle';
diff --git a/modules/currency.js b/modules/currency.js
index a59a9880af1..289ec8fbf69 100644
--- a/modules/currency.js
+++ b/modules/currency.js
@@ -5,6 +5,7 @@ import CONSTANTS from '../src/constants.json';
import { ajax } from '../src/ajax.js';
import { config } from '../src/config.js';
import { getHook } from '../src/hook.js';
+import {promiseControls} from '../src/utils/promise.js';
const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$';
const CURRENCY_RATE_PRECISION = 4;
@@ -21,22 +22,12 @@ var bidderCurrencyDefault = {};
var defaultRates;
export const ready = (() => {
- let isDone, resolver, promise;
+ let ctl;
function reset() {
- isDone = false;
- resolver = null;
- promise = new Promise((resolve) => {
- resolver = resolve;
- if (isDone) resolve();
- })
- }
- function done() {
- isDone = true;
- if (resolver != null) { resolver() }
+ ctl = promiseControls();
}
reset();
-
- return {done, reset, promise: () => promise}
+ return {done: () => ctl.resolve(), reset, promise: () => ctl.promise}
})();
/**
@@ -168,6 +159,8 @@ function initCurrency(url) {
}
}
);
+ } else {
+ ready.done();
}
}
diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js
index c9caa78e5e7..c0a24b49a3c 100644
--- a/modules/cwireBidAdapter.js
+++ b/modules/cwireBidAdapter.js
@@ -1,22 +1,22 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import { getRefererInfo } from '../src/refererDetection.js';
-import { getStorageManager } from '../src/storageManager.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 {OUTSTREAM} from '../src/video.js';
import {
- isArray,
- isNumber,
- generateUUID,
- parseSizesInput,
deepAccess,
+ generateUUID,
+ getBidIdParameter,
getParameterByName,
getValue,
- getBidIdParameter,
+ isArray,
+ isNumber,
logError,
logWarn,
+ parseSizesInput,
} from '../src/utils.js';
-import { Renderer } from '../src/Renderer.js';
-import find from 'core-js-pure/features/array/find.js';
+import {Renderer} from '../src/Renderer.js';
+import {find} from '../src/polyfill.js';
// ------------------------------------
const BIDDER_CODE = 'cwire';
@@ -28,7 +28,7 @@ const LS_CWID_KEY = 'cw_cwid';
const CW_GROUPS_QUERY = 'cwgroups';
const CW_CREATIVE_QUERY = 'cwcreative';
-const storage = getStorageManager();
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
/**
* ------------------------------------
@@ -91,14 +91,17 @@ export const mapSlotsData = function(validBidRequests) {
const slots = [];
validBidRequests.forEach(bid => {
const bidObj = {};
+ // get testing / debug params
+ let cwcreative = getValue(bid.params, 'cwcreative');
+ let refgroups = getValue(bid.params, 'refgroups');
+ let cwapikey = getValue(bid.params, 'cwapikey');
+
// 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;
@@ -106,6 +109,9 @@ export const mapSlotsData = function(validBidRequests) {
bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid);
bidObj.transactionId = getBidIdParameter('transactionId', bid);
bidObj.sizes = getSlotSizes(bid);
+ bidObj.cwcreative = cwcreative;
+ bidObj.refgroups = refgroups;
+ bidObj.cwapikey = cwapikey;
slots.push(bidObj);
});
@@ -124,11 +130,6 @@ export const spec = {
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;
@@ -142,6 +143,21 @@ export const spec = {
return true;
},
+ /**
+ * ------------------------------------
+ * itterate trough slots array and try
+ * to extract first occurence of a given
+ * key, if not found - return null
+ * ------------------------------------
+ */
+ getFirstValueOrNull: function(slots, key) {
+ const found = slots.find((item) => {
+ return (typeof item[key] !== 'undefined');
+ });
+
+ return (found) ? found[key] : null;
+ },
+
/**
* ------------------------------------
* Make a server request from the
@@ -162,9 +178,19 @@ export const spec = {
let refgroups = [];
- const cwCreativeId = getQueryVariable(CW_CREATIVE_QUERY);
+ const cwCreativeId = parseInt(getQueryVariable(CW_CREATIVE_QUERY), 10) || null;
+ const cwCreativeIdFromConfig = this.getFirstValueOrNull(slots, 'cwcreative');
+ const refGroupsFromConfig = this.getFirstValueOrNull(slots, 'refgroups');
+ const cwApiKeyFromConfig = this.getFirstValueOrNull(slots, 'cwapikey');
const rgQuery = getQueryVariable(CW_GROUPS_QUERY);
+
+ if (refGroupsFromConfig !== null) {
+ refgroups = refGroupsFromConfig.split(',');
+ }
+
if (rgQuery !== null) {
+ // override if query param is present
+ refgroups = [];
refgroups = rgQuery.split(',');
}
@@ -173,8 +199,9 @@ export const spec = {
const payload = {
cwid: localStorageCWID,
refgroups,
- cwcreative: cwCreativeId,
+ cwcreative: cwCreativeId || cwCreativeIdFromConfig,
slots: slots,
+ cwapikey: cwApiKeyFromConfig,
httpRef: referer || '',
pageViewId: CW_PAGE_VIEW_ID,
};
diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md
index fc0889e05ad..b42c7a02489 100644
--- a/modules/cwireBidAdapter.md
+++ b/modules/cwireBidAdapter.md
@@ -2,7 +2,7 @@
Module Name: C-WIRE Bid Adapter
Module Type: Adagio Adapter
-Maintainer: dragan@cwire.ch
+Maintainer: publishers@cwire.ch
## Description
@@ -17,7 +17,10 @@ Below, the list of C-WIRE params and where they can be set.
| ---------- | ------------- | ------------- | ---- | ---------|
| pageId | | x | number | YES |
| placementId | | x | number | YES |
-| adUnitElementId | | x | string | NO |
+| refgroups | | x | string | NO |
+| cwcreative | | x | integer | NO |
+| cwapikey | | x | string | NO |
+
### adUnit configuration
@@ -35,9 +38,11 @@ var adUnits = [
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
+ cwcreative: 42, // optional - id of creative to force
+ refgroups: 'test-user', // optional - name of group or coma separated list of groups to force
+ cwapikey: 'api_key_xyz', // optional - api key for integration testing
}
}]
}
];
-```
\ No newline at end of file
+```
diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js
new file mode 100644
index 00000000000..73b5c7420cf
--- /dev/null
+++ b/modules/dacIdSystem.js
@@ -0,0 +1,57 @@
+/**
+ * This module adds dacId to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/dacIdSystem
+ * @requires module:modules/userId
+ */
+
+import { submodule } from '../src/hook.js';
+import { getStorageManager } from '../src/storageManager.js';
+
+export const storage = getStorageManager();
+
+export const cookieKey = '_a1_f';
+
+export const dacIdSystemSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: 'dacId',
+
+ /**
+ * performs action to obtain id
+ * @function
+ * @returns { {id: {dacId: string}} | undefined }
+ */
+ getId: function() {
+ const newId = storage.getCookie(cookieKey);
+ if (!newId) {
+ return undefined;
+ }
+ const result = {
+ dacId: newId
+ }
+ return {id: result};
+ },
+
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function
+ * @param { {dacId: string} } value
+ * @returns { {dacId: {id: string} } | undefined }
+ */
+ decode: function(value) {
+ if (value && typeof value === 'object') {
+ const result = {};
+ if (value.dacId) {
+ result.id = value.dacId
+ }
+ return {dacId: result};
+ }
+ return undefined;
+ },
+
+}
+
+submodule('userId', dacIdSystemSubmodule);
diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md
new file mode 100644
index 00000000000..b422d0a536d
--- /dev/null
+++ b/modules/dacIdSystem.md
@@ -0,0 +1,28 @@
+## DAC User ID Submodule
+
+DAC ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie.
+Please contact D.A.Consortium Inc. before using this ID.
+
+## Building Prebid with DAC ID Support
+
+First, make sure to add the DAC ID submodule to your Prebid.js package with:
+
+```
+gulp build --modules=dacIdSystem
+```
+
+The following configuration parameters are available:
+
+```javascript
+pbjs.setConfig({
+ userSync: {
+ userIds: [{
+ name: 'dacId'
+ }]
+ }
+});
+```
+
+| Param under userSync.userIds[] | Scope | Type | Description | Example |
+| --- | --- | --- | --- | --- |
+| name | Required | String | The name of this module. | `"dacId"` |
diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js
index cdcc9f1d038..ffa84ff88fd 100644
--- a/modules/dailyhuntBidAdapter.js
+++ b/modules/dailyhuntBidAdapter.js
@@ -1,9 +1,9 @@
-import { registerBidder } from '../src/adapters/bidderFactory.js';
+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';
+import {_map, deepAccess, isEmpty} from '../src/utils.js';
+import {ajax} from '../src/ajax.js';
+import {find} from '../src/polyfill.js';
+import {INSTREAM, OUTSTREAM} from '../src/video.js';
const BIDDER_CODE = 'dailyhunt';
const BIDDER_ALIAS = 'dh';
diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js
index 43039e070c3..b240db1dd25 100644
--- a/modules/datablocksBidAdapter.js
+++ b/modules/datablocksBidAdapter.js
@@ -4,7 +4,7 @@ import { config } from '../src/config.js';
import { BANNER, NATIVE } from '../src/mediaTypes.js';
import { getStorageManager } from '../src/storageManager.js';
import { ajax } from '../src/ajax.js';
-export const storage = getStorageManager();
+export const storage = getStorageManager({bidderCode: 'datablocks'});
const NATIVE_ID_MAP = {};
const NATIVE_PARAMS = {
diff --git a/modules/dchain.js b/modules/dchain.js
index 6a1bd1ebf70..fbe78fc5c86 100644
--- a/modules/dchain.js
+++ b/modules/dchain.js
@@ -1,7 +1,7 @@
-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';
+import {includes} from '../src/polyfill.js';
+import {config} from '../src/config.js';
+import {getHook} from '../src/hook.js';
+import {_each, deepAccess, deepClone, hasOwn, isArray, isPlainObject, isStr, logError, logWarn} from '../src/utils.js';
const shouldBeAString = ' should be a string';
const shouldBeAnObject = ' should be an object';
diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js
index 5af2384cad9..c8de1ed9753 100644
--- a/modules/debugging/pbsInterceptor.js
+++ b/modules/debugging/pbsInterceptor.js
@@ -1,6 +1,6 @@
import {deepClone, delayExecution} from '../../src/utils.js';
import {createBid} from '../../src/bidfactory.js';
-import * as CONSTANTS from '../../src/constants.json';
+import {default as CONSTANTS} from '../../src/constants.json';
export function pbsBidInterceptor (next, interceptBids, s2sBidRequest, bidRequests, ajax, {
onResponse,
diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js
index 375c8c07ed1..43c7af1b3cc 100644
--- a/modules/deepintentDpesIdSystem.js
+++ b/modules/deepintentDpesIdSystem.js
@@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
const MODULE_NAME = 'deepintentId';
-export const storage = getStorageManager(null, MODULE_NAME);
+export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME});
/** @type {Submodule} */
export const deepintentDpesSubmodule = {
diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js
index 79cb03ec001..37f038d2a67 100644
--- a/modules/dfpAdServerVideo.js
+++ b/modules/dfpAdServerVideo.js
@@ -9,8 +9,9 @@ import { config } from '../src/config.js';
import { getHook, submodule } from '../src/hook.js';
import { auctionManager } from '../src/auctionManager.js';
import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js';
-import events from '../src/events.js';
+import * as events from '../src/events.js';
import CONSTANTS from '../src/constants.json';
+import {getPPID} from '../src/adserver.js';
/**
* @typedef {Object} DfpVideoParams
@@ -88,7 +89,14 @@ export function buildDfpVideoUrl(options) {
sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'),
url: encodeURIComponent(location.href),
};
- const encodedCustomParams = getCustParams(bid, options);
+
+ const urlSearchComponent = urlComponents.search;
+ const urlSzParam = urlSearchComponent && urlSearchComponent.sz
+ if (urlSzParam) {
+ derivedParams.sz = urlSzParam + '|' + derivedParams.sz;
+ }
+
+ let encodedCustomParams = getCustParams(bid, options, urlSearchComponent && urlSearchComponent.cust_params);
const queryParams = Object.assign({},
defaultParamConstants,
@@ -111,12 +119,18 @@ export function buildDfpVideoUrl(options) {
const uspConsent = uspDataHandler.getConsentData();
if (uspConsent) { queryParams.us_privacy = uspConsent; }
- return buildUrl({
+ if (!queryParams.ppid) {
+ const ppid = getPPID();
+ if (ppid != null) {
+ queryParams.ppid = ppid;
+ }
+ }
+
+ return buildUrl(Object.assign({
protocol: 'https',
host: 'securepubads.g.doubleclick.net',
- pathname: '/gampad/ads',
- search: queryParams
- });
+ pathname: '/gampad/ads'
+ }, urlComponents, { search: queryParams }));
}
export function notifyTranslationModule(fn) {
@@ -227,9 +241,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) {
const descriptionUrl = getDescriptionUrl(bid, components, 'search');
if (descriptionUrl) { components.search.description_url = descriptionUrl; }
- const encodedCustomParams = getCustParams(bid, options);
- components.search.cust_params = (components.search.cust_params) ? components.search.cust_params + '%26' + encodedCustomParams : encodedCustomParams;
-
+ components.search.cust_params = getCustParams(bid, options, components.search.cust_params);
return buildUrl(components);
}
@@ -258,7 +270,7 @@ function getDescriptionUrl(bid, components, prop) {
* @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function
* @return {Object} Encoded key value pairs for cust_params
*/
-function getCustParams(bid, options) {
+function getCustParams(bid, options, urlCustParams) {
const adserverTargeting = (bid && bid.adserverTargeting) || {};
let allTargetingData = {};
@@ -281,7 +293,12 @@ function getCustParams(bid, options) {
// merge the prebid + publisher targeting sets
const publisherTargetingSet = deepAccess(options, 'params.cust_params');
const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet);
- return encodeURIComponent(formatQS(targetingSet));
+ let encodedParams = encodeURIComponent(formatQS(targetingSet));
+ if (urlCustParams) {
+ encodedParams = urlCustParams + '%26' + encodedParams;
+ }
+
+ return encodedParams;
}
registerVideoSupport('dfp', {
diff --git a/modules/distroscaleBidAdapter.js b/modules/distroscaleBidAdapter.js
new file mode 100644
index 00000000000..822bea3603a
--- /dev/null
+++ b/modules/distroscaleBidAdapter.js
@@ -0,0 +1,262 @@
+import { logWarn, isPlainObject, isStr, isArray, isFn, inIframe, mergeDeep, deepSetValue, logError, deepClone } from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { BANNER } from '../src/mediaTypes.js';
+const BIDDER_CODE = 'distroscale';
+const SHORT_CODE = 'ds';
+const LOG_WARN_PREFIX = 'DistroScale: ';
+const ENDPOINT = 'https://hb.jsrdn.com/hb?from=pbjs';
+const DEFAULT_CURRENCY = 'USD';
+const AUCTION_TYPE = 1;
+const GVLID = 754;
+const UNDEF = undefined;
+
+const SUPPORTED_MEDIATYPES = [ BANNER ];
+
+function _getHost(url) {
+ let a = document.createElement('a');
+ a.href = url;
+ return a.hostname;
+}
+
+function _getBidFloor(bid, mType, sz) {
+ if (isFn(bid.getFloor)) {
+ let floor = bid.getFloor({
+ currency: DEFAULT_CURRENCY,
+ mediaType: mType || '*',
+ size: sz || '*'
+ });
+ if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) {
+ return floor.floor;
+ }
+ }
+ return null;
+}
+
+function _createImpressionObject(bid) {
+ var impObj = UNDEF;
+ var i;
+ var sizes = {};
+ var sizesCount = 0;
+
+ function addSize(arr) {
+ var w, h;
+ if (arr && arr.length > 1) {
+ w = parseInt(arr[0]);
+ h = parseInt(arr[1]);
+ }
+ sizes[w + 'x' + h] = {
+ w: w,
+ h: h,
+ area: w * h,
+ idx:
+ ({
+ '970x250': 1,
+ '300x250': 2
+ })[w + 'x' + h] || Math.max(w * h, 200)
+ };
+ sizesCount++;
+ }
+
+ // Gather all sizes
+ if (isArray(bid.sizes)) {
+ for (i = 0; i < bid.sizes.length; i++) {
+ addSize(bid.sizes[i]);
+ }
+ }
+ if (bid.params && bid.params.width && bid.params.height) {
+ addSize([bid.params.width, bid.params.height]);
+ }
+ if (bid.mediaTypes && BANNER in bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) {
+ for (i = 0; i < bid.mediaTypes[BANNER].sizes.length; i++) {
+ addSize(bid.mediaTypes[BANNER].sizes[i]);
+ }
+ }
+ if (sizesCount == 0) {
+ logWarn(LOG_WARN_PREFIX + 'Error: missing sizes: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.');
+ } else {
+ // Use the first preferred size
+ var keys = Object.keys(sizes);
+ keys.sort(function(a, b) {
+ return sizes[a].idx - sizes[b].idx
+ });
+ var bannerObj = {
+ pos: 0,
+ w: sizes[keys[0]].w,
+ h: sizes[keys[0]].h,
+ topframe: inIframe() ? 0 : 1,
+ format: [{
+ 'w': sizes[keys[0]].w,
+ 'h': sizes[keys[0]].h
+ }]
+ };
+
+ impObj = {
+ id: bid.bidId,
+ tagid: bid.params.zoneid || '',
+ secure: 1,
+ ext: {
+ pubid: bid.params.pubid || '',
+ zoneid: bid.params.zoneid || ''
+ }
+ };
+
+ var floor = _getBidFloor(bid, BANNER, [sizes[keys[0]].w, sizes[keys[0]].h]);
+ if (floor > 0) {
+ impObj.bidfloor = floor;
+ impObj.bidfloorcur = DEFAULT_CURRENCY;
+ }
+
+ impObj[BANNER] = bannerObj;
+ }
+
+ return impObj;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: GVLID,
+ supportedMediaTypes: SUPPORTED_MEDIATYPES,
+ aliases: [SHORT_CODE],
+
+ isBidRequestValid: bid => {
+ if (bid && bid.params && bid.params.pubid && isStr(bid.params.pubid)) {
+ return true;
+ } else {
+ logWarn(LOG_WARN_PREFIX + 'Error: pubid is mandatory and cannot be numeric');
+ }
+ return false;
+ },
+
+ buildRequests: (validBidRequests, bidderRequest) => {
+ var pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || window.location.href;
+
+ var payload = {
+ id: '' + (new Date()).getTime(),
+ at: AUCTION_TYPE,
+ cur: [DEFAULT_CURRENCY],
+ site: {
+ page: pageUrl
+ },
+ device: {
+ ua: navigator.userAgent,
+ js: 1,
+ h: screen.height,
+ w: screen.width,
+ language: (navigator.language && navigator.language.replace(/-.*/, '')) || 'en',
+ dnt: (navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1' || navigator.doNotTrack == 'yes') ? 1 : 0
+ },
+ imp: [],
+ user: {},
+ ext: {}
+ };
+
+ validBidRequests.forEach(b => {
+ var bid = deepClone(b);
+ var impObj = _createImpressionObject(bid);
+ if (impObj) {
+ payload.imp.push(impObj);
+ }
+ });
+
+ if (payload.imp.length == 0) {
+ return;
+ }
+
+ payload.site.domain = _getHost(payload.site.page);
+
+ // add the content object from config in request
+ if (typeof config.getConfig('content') === 'object') {
+ payload.site.content = config.getConfig('content');
+ }
+
+ // merge the device from config.getConfig('device')
+ if (typeof config.getConfig('device') === 'object') {
+ payload.device = Object.assign(payload.device, config.getConfig('device'));
+ }
+
+ // adding schain object
+ if (validBidRequests[0].schain) {
+ deepSetValue(payload, 'source.schain', validBidRequests[0].schain);
+ }
+
+ // Attaching GDPR Consent Params
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ deepSetValue(payload, 'user.consent', bidderRequest.gdprConsent.consentString);
+ deepSetValue(payload, 'regs.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0));
+ }
+
+ // CCPA
+ if (bidderRequest && bidderRequest.uspConsent) {
+ deepSetValue(payload, 'regs.us_privacy', bidderRequest.uspConsent);
+ }
+
+ // coppa compliance
+ if (config.getConfig('coppa') === true) {
+ deepSetValue(payload, 'regs.coppa', 1);
+ }
+
+ // First Party Data
+ const commonFpd = config.getConfig('ortb2') || {};
+ if (commonFpd.site) {
+ mergeDeep(payload, {site: commonFpd.site});
+ }
+ if (commonFpd.user) {
+ mergeDeep(payload, {user: commonFpd.user});
+ }
+
+ // User IDs
+ if (validBidRequests[0].userIdAsEids && validBidRequests[0].userIdAsEids.length > 0) {
+ // Standard ORTB structure
+ deepSetValue(payload, 'user.eids', validBidRequests[0].userIdAsEids);
+ } else if (validBidRequests[0].userId && Object.keys(validBidRequests[0].userId).length > 0) {
+ // Fallback to non-ortb structure
+ deepSetValue(payload, 'user.ext.userId', validBidRequests[0].userId);
+ }
+
+ return {
+ method: 'POST',
+ url: ENDPOINT,
+ data: payload,
+ bidderRequest: bidderRequest
+ };
+ },
+
+ interpretResponse: (response, request) => {
+ const bidResponses = [];
+ try {
+ if (response.body && response.body.seatbid && isArray(response.body.seatbid)) {
+ // Supporting multiple bid responses for same adSize
+ response.body.seatbid.forEach(seatbidder => {
+ seatbidder.bid &&
+ isArray(seatbidder.bid) &&
+ seatbidder.bid.forEach(bid => {
+ let newBid = {
+ requestId: bid.impid,
+ cpm: (parseFloat(bid.price) || 0),
+ currency: DEFAULT_CURRENCY,
+ width: parseInt(bid.w),
+ height: parseInt(bid.h),
+ creativeId: bid.crid || bid.id,
+ netRevenue: true,
+ ttl: 300,
+ ad: bid.adm,
+ meta: {
+ advertiserDomains: []
+ }
+ };
+ if (isArray(bid.adomain) && bid.adomain.length > 0) {
+ newBid.meta.advertiserDomains = bid.adomain;
+ }
+ bidResponses.push(newBid);
+ });
+ });
+ }
+ } catch (error) {
+ logError(error);
+ }
+ return bidResponses;
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/distroscaleBidAdapter.md b/modules/distroscaleBidAdapter.md
new file mode 100644
index 00000000000..1d7948b2a02
--- /dev/null
+++ b/modules/distroscaleBidAdapter.md
@@ -0,0 +1,30 @@
+# Overview
+
+```
+Module Name: DistroScale Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: prebid@distroscale.com
+```
+
+# Description
+
+Connects to DistroScale exchange for bids. DistroScale bid adapter supports Banner currently.
+
+# Test Parameters
+```
+var adUnits = [{
+ code: 'banner-1',
+ mediaTypes: {
+ banner: {
+ sizes: [[970, 250]],
+ }
+ },
+ bids: [{
+ bidder: 'distroscale',
+ params: {
+ pubid: '12345' // required, must be a string
+ ,zoneid: '67890' // optional, must be a string
+ }
+ }]
+}];
+```
diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js
index 884c4f0c067..63332db8725 100644
--- a/modules/e_volutionBidAdapter.js
+++ b/modules/e_volutionBidAdapter.js
@@ -41,6 +41,19 @@ function getBidFloor(bid) {
}
}
+function getUserId(eids, id, source, uidExt) {
+ if (id) {
+ var uid = { id };
+ if (uidExt) {
+ uid.ext = uidExt;
+ }
+ eids.push({
+ source,
+ uids: [ uid ]
+ });
+ }
+}
+
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
@@ -86,7 +99,12 @@ export const spec = {
const placement = {
placementId: bid.params.placementId,
bidId: bid.bidId,
- bidfloor: getBidFloor(bid)
+ bidfloor: getBidFloor(bid),
+ eids: []
+ }
+
+ if (bid.userId) {
+ getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com');
}
if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) {
diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js
index 0ed23f11631..66fd2eb2ac1 100644
--- a/modules/emx_digitalBidAdapter.js
+++ b/modules/emx_digitalBidAdapter.js
@@ -1,9 +1,19 @@
-import { isArray, logWarn, logError, parseUrl, deepAccess, isStr, _each, getBidIdParameter, isFn, isPlainObject } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { Renderer } from '../src/Renderer.js';
-import includes from 'core-js-pure/features/array/includes.js';
-import find from 'core-js-pure/features/array/find.js';
+import {
+ _each,
+ deepAccess,
+ getBidIdParameter,
+ isArray,
+ isFn,
+ isPlainObject,
+ isStr,
+ logError,
+ logWarn,
+ parseUrl
+} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {Renderer} from '../src/Renderer.js';
+import {find, includes} from '../src/polyfill.js';
const BIDDER_CODE = 'emx_digital';
const ENDPOINT = 'hb.emxdgt.com';
diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js
index 3e0f1d443b1..95ab8ecbd03 100644
--- a/modules/engageyaBidAdapter.js
+++ b/modules/engageyaBidAdapter.js
@@ -1,12 +1,10 @@
import { BANNER, NATIVE } from '../src/mediaTypes.js';
import { createTrackPixelHtml } from '../src/utils.js';
-
-const {
- registerBidder
-} = require('../src/adapters/bidderFactory.js');
+import {registerBidder} from '../src/adapters/bidderFactory.js';
const BIDDER_CODE = 'engageya';
const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json';
const ENDPOINT_METHOD = 'GET';
+const MAX_DEVIATION = 0.05;
const SUPPORTED_SIZES = [
[100, 75], [236, 202], [100, 100], [130, 130], [200, 200], [250, 250], [300, 272], [300, 250], [300, 230], [300, 214], [300, 187], [300, 166], [300, 150], [300, 133], [300, 120], [400, 200], [300, 200], [250, 377], [620, 410], [207, 311], [310, 166], [310, 333], [190, 106], [228, 132], [300, 174], [80, 60], [600, 500], [600, 600], [1080, 610], [1080, 610], [624, 350], [650, 1168], [1080, 1920], [300, 374], [336, 280]
];
@@ -98,7 +96,18 @@ function isValidSize([width, height]) {
if (!width || !height) {
return false;
}
- return SUPPORTED_SIZES.some(([supportedWidth, supportedHeight]) => supportedWidth === width && supportedHeight === height);
+ return SUPPORTED_SIZES.some(([supportedWidth, supportedHeight]) => {
+ if (supportedWidth === width && supportedHeight === height) {
+ return true;
+ }
+ const supportedRatio = supportedWidth / supportedHeight;
+ const ratioDeviation = supportedRatio / width * height;
+ if (Math.abs(ratioDeviation - 1) > MAX_DEVIATION) {
+ return false;
+ }
+ return supportedWidth > width ||
+ (width - supportedWidth) / width <= MAX_DEVIATION;
+ });
}
export const spec = {
diff --git a/modules/engageyaBidAdapter.md b/modules/engageyaBidAdapter.md
index 541ba548eeb..67205350f71 100644
--- a/modules/engageyaBidAdapter.md
+++ b/modules/engageyaBidAdapter.md
@@ -3,7 +3,7 @@
```
Module Name: Engageya's Bidder Adapter
Module Type: Bidder Adapter
-Maintainer: reem@engageya.com
+Maintainer: prebid@engageya.com
```
# Description
@@ -65,4 +65,4 @@ Module that connects to Engageya's demand sources
]
}
];
-```
\ No newline at end of file
+```
diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js
index fb77014400c..365f91382f7 100644
--- a/modules/eplanningAnalyticsAdapter.js
+++ b/modules/eplanningAnalyticsAdapter.js
@@ -2,8 +2,7 @@ import { logError } from '../src/utils.js';
import {ajax} from '../src/ajax.js';
import adapter from '../src/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
-
-const CONSTANTS = require('../src/constants.json');
+import CONSTANTS from '../src/constants.json';
const analyticsType = 'endpoint';
const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/';
diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js
index 330754091a1..780531964ad 100644
--- a/modules/eplanningBidAdapter.js
+++ b/modules/eplanningBidAdapter.js
@@ -3,9 +3,8 @@ import { getGlobal } from '../src/prebidGlobal.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
-export const storage = getStorageManager();
-
const BIDDER_CODE = 'eplanning';
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
const rnd = Math.random();
const DEFAULT_SV = 'pbjs.e-planning.net';
const DEFAULT_ISV = 'i.e-planning.net';
diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js
index 12abd2d0efd..7ecc7e963b5 100644
--- a/modules/fintezaAnalyticsAdapter.js
+++ b/modules/fintezaAnalyticsAdapter.js
@@ -3,9 +3,9 @@ import { ajax } from '../src/ajax.js';
import adapter from '../src/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import { getStorageManager } from '../src/storageManager.js';
+import CONSTANTS from '../src/constants.json';
const storage = getStorageManager();
-const CONSTANTS = require('../src/constants.json');
const ANALYTICS_TYPE = 'endpoint';
const FINTEZA_HOST = 'https://content.mql5.com/tr';
diff --git a/modules/flocIdSystem.js b/modules/flocIdSystem.js
index 0cff7e86d73..3fddbaa7129 100644
--- a/modules/flocIdSystem.js
+++ b/modules/flocIdSystem.js
@@ -28,12 +28,7 @@ function enableOriginTrial(token) {
* @param errorCallback
*/
function getFlocData(successCallback, errorCallback) {
- document.interestCohort()
- .then((data) => {
- successCallback(data);
- }).catch((error) => {
- errorCallback(error);
- });
+ errorCallback('The Floc has flown');
}
/**
@@ -82,7 +77,7 @@ export const flocIdSubmodule = {
return;
}
// Validate feature is enabled
- const isFlocEnabled = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime) && !!document.featurePolicy && !!document.featurePolicy.features() && document.featurePolicy.features().includes('interest-cohort');
+ const isFlocEnabled = false;
if (isFlocEnabled) {
const configParams = (config && config.params) || {};
diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js
index b7cc848edc4..44b9f3bf217 100644
--- a/modules/fluctBidAdapter.js
+++ b/modules/fluctBidAdapter.js
@@ -1,6 +1,5 @@
import { _each, isEmpty } from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import URLSearchParams from 'core-js-pure/web/url-search-params'
const BIDDER_CODE = 'fluct';
const END_POINT = 'https://hb.adingo.jp/prebid';
diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js
new file mode 100644
index 00000000000..21206109ee0
--- /dev/null
+++ b/modules/ftrackIdSystem.js
@@ -0,0 +1,198 @@
+/**
+ * This module adds ftrack to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/ftrack
+ * @requires module:modules/userId
+ */
+
+import * as utils from '../src/utils.js';
+import { submodule } from '../src/hook.js';
+import { getStorageManager } from '../src/storageManager.js';
+import { uspDataHandler } from '../src/adapterManager.js';
+
+const MODULE_NAME = 'ftrackId';
+const LOG_PREFIX = 'FTRACK - ';
+const LOCAL_STORAGE_EXP_DAYS = 30;
+const VENDOR_ID = null;
+const LOCAL_STORAGE = 'html5';
+const FTRACK_STORAGE_NAME = 'ftrackId';
+const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`;
+const FTRACK_URL = 'https://d9.flashtalking.com/d9core';
+const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME});
+
+let consentInfo = {
+ gdpr: {
+ applies: 0,
+ consentString: null,
+ pd: null
+ },
+ usPrivacy: {
+ value: null
+ }
+};
+
+/** @type {Submodule} */
+export const ftrackIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: `ftrack`,
+
+ /**
+ * Decodes the 'value'
+ * @function decode (required method)
+ * @param {(Object|string)} value
+ * @param {SubmoduleConfig|undefined} config
+ * @returns {(Object|undefined)} an object with the key being ideally camel case
+ * similar to the module name and ending in id or Id
+ */
+ decode (value, config) {
+ return {
+ ftrackId: value
+ };
+ },
+
+ /**
+ * performs action(s) to obtain ids from D9 and return the Device IDs
+ * should be the only method that gets a new ID (from ajax calls or a cookie/local storage)
+ * @function getId (required method)
+ * @param {SubmoduleConfig} config
+ * @param {ConsentData} consentData
+ * @param {(Object|undefined)} cacheIdObj
+ * @returns {IdResponse|undefined}
+ */
+ getId (config, consentData, cacheIdObj) {
+ if (this.isConfigOk(config) === false || this.isThereConsent(consentData) === false) return undefined;
+
+ return {
+ callback: function () {
+ window.D9v = {
+ UserID: '99999999999999',
+ CampID: '3175',
+ CCampID: '148556'
+ };
+ window.D9r = {
+ callback: function(response) {
+ if (response) {
+ storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString());
+ storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}`, JSON.stringify(response));
+
+ storage.setDataInLocalStorage(`${FTRACK_PRIVACY_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString());
+ storage.setDataInLocalStorage(`${FTRACK_PRIVACY_STORAGE_NAME}`, JSON.stringify(consentInfo));
+ };
+
+ return response;
+ }
+ };
+
+ // If config.params.ids does not exist, set defaults
+ if (!config.params.hasOwnProperty('ids')) {
+ window.D9r.DeviceID = true;
+ window.D9r.SingleDeviceID = true;
+ } else {
+ if (config.params.ids.hasOwnProperty('device id') && config.params.ids['device id'] === true) {
+ window.D9r.DeviceID = true;
+ }
+ if (config.params.ids.hasOwnProperty('single device id') && config.params.ids['single device id'] === true) {
+ window.D9r.SingleDeviceID = true;
+ }
+ if (config.params.ids.hasOwnProperty('household id') && config.params.ids['household id'] === true) {
+ window.D9r.HHID = true;
+ }
+ }
+
+ if (config.params && config.params.url && config.params.url === FTRACK_URL) {
+ var ftrackScript = document.createElement('script');
+ ftrackScript.setAttribute('src', config.params.url);
+ window.document.body.appendChild(ftrackScript);
+ }
+ }
+ };
+ },
+
+ /**
+ * Called when IDs are already in localStorage
+ * should just be adding additional data to the cacheIdObj object
+ * @function extendId (optional method)
+ * @param {SubmoduleConfig} config
+ * @param {ConsentData} consentData
+ * @param {(Object|undefined)} cacheIdObj
+ * @returns {IdResponse|undefined}
+ */
+ extendId (config, consentData, cacheIdObj) {
+ this.isConfigOk(config);
+ return cacheIdObj;
+ },
+
+ /*
+ * Validates the config, if it is not correct, then info cannot be saved in localstorage
+ * @function isConfigOk
+ * @param {SubmoduleConfig} config from HTML
+ * @returns {true|false}
+ */
+ isConfigOk: function(config) {
+ if (!config.storage || !config.storage.type || !config.storage.name) {
+ utils.logError(LOG_PREFIX + 'config.storage required to be set.');
+ return false;
+ }
+
+ // in a future release, we may return false if storage type or name are not set as required
+ if (config.storage.type !== LOCAL_STORAGE) {
+ utils.logWarn(LOG_PREFIX + 'config.storage.type recommended to be "' + LOCAL_STORAGE + '".');
+ }
+ // in a future release, we may return false if storage type or name are not set as required
+ if (config.storage.name !== FTRACK_STORAGE_NAME) {
+ utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".');
+ }
+
+ if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url') || config.params.url !== FTRACK_URL) {
+ utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run. Url should be "' + FTRACK_URL + '".');
+ return false;
+ }
+
+ return true;
+ },
+
+ isThereConsent: function(consentData) {
+ let consentValue = true;
+
+ /*
+ * Scenario 1: GDPR
+ * if GDPR Applies is true|1, we do not have consent
+ * if GDPR Applies does not exist or is false|0, we do not NOT have consent
+ */
+ if (consentData && consentData.gdprApplies && (consentData.gdprApplies === true || consentData.gdprApplies === 1)) {
+ consentInfo.gdpr.applies = 1;
+ consentValue = false;
+ }
+ // If consentString exists, then we store it even though we are not using it
+ if (consentData && consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) {
+ consentInfo.gdpr.consentString = consentData.consentString;
+ }
+
+ /*
+ * Scenario 2: CCPA/us_privacy
+ * if usp exists (assuming this check determines the location of the device to be within the California)
+ * parse the us_privacy string to see if we have consent
+ * for version 1 of us_privacy strings, if 'Opt-Out Sale' is 'Y' we do not track
+ */
+ const usp = uspDataHandler.getConsentData();
+ let usPrivacyVersion;
+ // let usPrivacyOptOut;
+ let usPrivacyOptOutSale;
+ // let usPrivacyLSPA;
+ if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) {
+ consentInfo.usPrivacy.value = usp;
+ usPrivacyVersion = usp[0];
+ // usPrivacyOptOut = usp[1];
+ usPrivacyOptOutSale = usp[2];
+ // usPrivacyLSPA = usp[3];
+ }
+ if (usPrivacyVersion == 1 && usPrivacyOptOutSale === 'Y') consentValue = false;
+
+ return consentValue;
+ }
+};
+
+submodule('userId', ftrackIdSubmodule);
diff --git a/modules/ftrackIdSystem.md b/modules/ftrackIdSystem.md
new file mode 100644
index 00000000000..0c92f5afab1
--- /dev/null
+++ b/modules/ftrackIdSystem.md
@@ -0,0 +1,83 @@
+# Flashtalking's FTrack Identity Framework User ID Module
+
+*The FTrack Identity Framework User ID Module allows publishers to take advantage of Flashtalking's FTrack ID during the bidding process.*
+
+### [FTrack](https://www.flashtalking.com/identity-framework#FTrack)
+
+Flashtalking’s cookieless tracking technology uses probabilistic device recognition to derive a privacy-friendly persistent ID for each device.
+
+**ANTI-FINGERPRINTING**
+FTrack operates in strict compliance with [Google’s definition of anti-fingerprinting](https://blog.google/products/ads-commerce/2021-01-privacy-sandbox/). FTrack does not access PII or sensitive information and provides consumers with notification and choice on every impression. We do not participate in the types of activities that most concern privacy advocates (profiling consumers, building audience segments, and/or monetizing consumer data).
+
+**GDPR COMPLIANT**
+Flashtalking is integrated with the IAB EU’s Transparency & Consent Framework (TCF) and operates on a Consent legal basis where required. As a Data Processor under GDPR, Flashtalking does not combine data across customers nor sell data to third parties.
+
+---
+
+### Support or Maintenance:
+
+Questions? Comments? Bugs? Praise? Please contact FlashTalking's Prebid Support at [prebid-support@flashtalking.com](mailto:prebid-support@flashtalking.com)
+
+---
+
+### FTrack User ID Configuration
+
+The following configuration parameters are available:
+
+```javascript
+pbjs.setConfig({
+ userSync: {
+ userIds: [{
+ name: 'FTrack',
+ params: {
+ url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run
+ ids: {
+ 'device id': true,
+ 'single device id': true,
+ 'household id': true
+ }
+ },
+ storage: {
+ type: 'html5', // "html5" is the required storage type
+ name: 'FTrackId', // "FTrackId" is the required storage name
+ expires: 90, // storage lasts for 90 days
+ refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh
+ }
+ }],
+ auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules
+ }
+});
+```
+
+| Param under userSync.userIds[] | Scope | Type | Description | Example |
+| :-- | :-- | :-- | :-- | :-- |
+| name | Required | String | The name of this module: `"FTrack"` | `"FTrack"` |
+| params | Required | Object | The IDs available, if not populated then the defaults "Device ID" and "Single Device ID" will be returned | |
+| params.url | Required | String | The URL for the ftrack library reference. If not populate, ftrack will not run. | 'https://d9.flashtalking.com/d9core' |
+| params.ids | Optional | Object | The ftrack IDs available, if not populated then the defaults "Device ID" and "Single Device ID" will be returned | |
+| params.ids['device id'] | Optional | Boolean | Should ftrack return "device id". Set to `true` to return it. If set to `undefined` or `false`, ftrack will not return "device id". Default is `false` | `true` |
+| params.ids['single device id'] | Optional | Boolean | Should ftrack return "single device id". Set to `true` to return it. If set to `undefined` or `false`, ftrack will not return "single device id". Default is `false` | `true` |
+| params.ids['household id'] | Optional; _Requires pairing with either "device id" or "single device id"_ | Boolean | __1.__ Should ftrack return "household id". Set to `true` to attempt to return it. If set to `undefined` or `false`, ftrack will not return "household id". Default is `false`. __2.__ _This will only return "household id" if value of this field is `true` **AND** "household id" is defined on the device._ __3.__ _"household id" requires either "device id" or "single device id" to be also set to `true`, otherwise ftrack will not return "household id"._ | `true` |
+| storage | Required | Object | Storage settings for how the User ID module will cache the FTrack ID locally | |
+| storage.type | Required | String | This is where the results of the user ID will be stored. FTrack **requires** `"html5"`. | `"html5"` |
+| storage.name | Required | String | The name of the local storage where the user ID will be stored. FTrack **requires** `"FTrackId"`. | `"FTrackId"` |
+| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. FTrack recommends `90`. | `90` |
+| storage.refreshInSeconds | Optional | Integer | How many seconds until the FTrack ID will be refreshed. FTrack strongly recommends 8 hours between refreshes | `8*3600` |
+
+---
+
+### Privacy Policies.
+
+Complete information available on the Flashtalking [privacy policy page](https://www.flashtalking.com/privacypolicy).
+
+#### OPTING OUT OF INTEREST-BASED ADVERTISING & COLLECTION OF PERSONAL INFORMATION
+
+Please visit our [Opt Out Page](https://www.flashtalking.com/optout).
+
+#### REQUEST REMOVAL OF YOUR PERSONAL DATA (WHERE APPLICABLE)
+
+You may request by emailing [mailto:privacy@flashtalking.com](privacy@flashtalking.com).
+
+#### GDPR
+
+In its current state, Flashtalking’s FTrack Identity Framework User ID Module does not create an ID if a user's consentData is "truthy" (true, 1). In other words, if GDPR applies in any way to a user, FTrack does not create an ID.
\ No newline at end of file
diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js
index 34b164f26ca..22a70db0fab 100644
--- a/modules/gamoshiBidAdapter.js
+++ b/modules/gamoshiBidAdapter.js
@@ -1,9 +1,21 @@
-import { isFn, isPlainObject, isStr, isNumber, getDNT, deepSetValue, inIframe, isArray, deepAccess, logError, logWarn } from '../src/utils.js';
+import {
+ deepAccess,
+ deepSetValue,
+ getDNT,
+ inIframe,
+ isArray,
+ isFn,
+ isNumber,
+ isPlainObject,
+ isStr,
+ logError,
+ logWarn
+} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {config} from '../src/config.js';
import {Renderer} from '../src/Renderer.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {includes} from '../src/polyfill.js';
const ENDPOINTS = {
'gamoshi': 'https://rtb.gamoshi.io'
@@ -99,17 +111,11 @@ export const spec = {
source: {ext: {}},
regs: {ext: {}}
};
- const gdprConsent = bidderRequest.gdprConsent;
- if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) {
- rtbBidRequest.ext.gdpr_consent = {
- consent_string: gdprConsent.consentString,
- consent_required: gdprConsent.gdprApplies
- };
-
- deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.gdprApplies === true ? 1 : 0);
- deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consentString);
- }
+ const gdprConsent = getGdprConsent(bidderRequest);
+ rtbBidRequest.ext.gdpr_consent = gdprConsent;
+ deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.consent_required === true ? 1 : 0);
+ deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consent_string);
if (validBidRequests[0].schain) {
deepSetValue(rtbBidRequest, 'source.ext.schain', validBidRequests[0].schain);
@@ -185,6 +191,7 @@ export const spec = {
if (bidRequest && bidRequest.userId) {
addExternalUserId(eids, deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 'ID5ID');
addExternalUserId(eids, deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID');
+ addExternalUserId(eids, deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 'idl');
}
if (eids.length > 0) {
rtbBidRequest.user.ext.eids = eids;
@@ -361,4 +368,20 @@ function replaceMacros(url, macros) {
.replace('[US_PRIVACY]', macros.uspConsent);
}
+function getGdprConsent(bidderRequest) {
+ const gdprConsent = bidderRequest.gdprConsent;
+
+ if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) {
+ return {
+ consent_string: gdprConsent.consentString,
+ consent_required: gdprConsent.gdprApplies
+ };
+ }
+
+ return {
+ consent_required: false,
+ consent_string: '',
+ };
+}
+
registerBidder(spec);
diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js
index 978bd8de9e3..161f530f202 100644
--- a/modules/gdprEnforcement.js
+++ b/modules/gdprEnforcement.js
@@ -2,15 +2,14 @@
* This module gives publishers extra set of features to enforce individual purposes of TCF v2
*/
-import { deepAccess, logWarn, isArray, hasDeviceAccess } from '../src/utils.js';
-import { config } from '../src/config.js';
-import adapterManager, { gdprDataHandler } from '../src/adapterManager.js';
-import find from 'core-js-pure/features/array/find.js';
-import includes from 'core-js-pure/features/array/includes.js';
-import { registerSyncInner } from '../src/adapters/bidderFactory.js';
-import { getHook } from '../src/hook.js';
-import { validateStorageEnforcement } from '../src/storageManager.js';
-import events from '../src/events.js';
+import {deepAccess, hasDeviceAccess, isArray, logWarn} from '../src/utils.js';
+import {config} from '../src/config.js';
+import adapterManager, {gdprDataHandler} from '../src/adapterManager.js';
+import {find, includes} from '../src/polyfill.js';
+import {registerSyncInner} from '../src/adapters/bidderFactory.js';
+import {getHook} from '../src/hook.js';
+import {validateStorageEnforcement} from '../src/storageManager.js';
+import * as events from '../src/events.js';
import CONSTANTS from '../src/constants.json';
const TCF2 = {
diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js
index c6777ebe44e..48b2cd43c3b 100644
--- a/modules/gjirafaBidAdapter.js
+++ b/modules/gjirafaBidAdapter.js
@@ -9,7 +9,7 @@ const SIZE_SEPARATOR = ';';
const BISKO_ID = 'biskoId';
const STORAGE_ID = 'bisko-sid';
const SEGMENTS = 'biskoSegments';
-const storage = getStorageManager();
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js
index 64b987254e9..35aaf56c604 100644
--- a/modules/glimpseBidAdapter.js
+++ b/modules/glimpseBidAdapter.js
@@ -1,19 +1,27 @@
-import { BANNER } from '../src/mediaTypes.js'
-import { config } from '../src/config.js'
-import { getStorageManager } from '../src/storageManager.js'
-import { isArray } from '../src/utils.js'
-import { registerBidder } from '../src/adapters/bidderFactory.js'
-
-const storageManager = getStorageManager()
-
-const GVLID = 1012
-const BIDDER_CODE = 'glimpse'
-const ENDPOINT = 'https://api.glimpsevault.io/ads/serving/public/v1/prebid'
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { BANNER } from '../src/mediaTypes.js';
+import { getStorageManager } from '../src/storageManager.js';
+import {
+ isArray,
+ isEmpty,
+ isEmptyStr,
+ isStr,
+ isPlainObject,
+} from '../src/utils.js';
+
+const GVLID = 1012;
+const BIDDER_CODE = 'glimpse';
+const storageManager = getStorageManager({
+ gvlid: GVLID,
+ bidderCode: BIDDER_CODE,
+});
+const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid';
const LOCAL_STORAGE_KEY = {
vault: {
jwt: 'gp_vault_jwt',
},
-}
+};
export const spec = {
gvlid: GVLID,
@@ -21,126 +29,121 @@ export const spec = {
supportedMediaTypes: [BANNER],
/**
- * Determines whether or not the given bid request is valid
+ * Determines if the bid request is valid
* @param bid {BidRequest} The bid to validate
* @return {boolean}
*/
isBidRequestValid: (bid) => {
- return (
- hasValue(bid) &&
- hasValue(bid.params) &&
- hasStringValue(bid.params.placementId)
- )
+ const pid = bid?.params?.pid;
+ return isStr(pid) && !isEmptyStr(pid);
},
/**
- * Builds http request for Glimpse bids
+ * Builds the http request
* @param validBidRequests {BidRequest[]}
* @param bidderRequest {BidderRequest}
* @returns {ServerRequest}
*/
buildRequests: (validBidRequests, bidderRequest) => {
- const auth = getVaultJwt()
- const referer = getReferer(bidderRequest)
- const gdprConsent = getGdprConsentChoice(bidderRequest)
- const bidRequests = validBidRequests.map(processBidRequest)
- const firstPartyData = getFirstPartyData()
+ const url = buildQuery(bidderRequest);
+ const auth = getVaultJwt();
+ const referer = getReferer(bidderRequest);
+ const imp = validBidRequests.map(processBidRequest);
+ const fpd = getFirstPartyData();
const data = {
auth,
data: {
referer,
- gdprConsent,
- bidRequests,
- site: firstPartyData.site,
- user: firstPartyData.user,
- bidderCode: spec.code,
- }
- }
+ imp,
+ fpd,
+ },
+ };
return {
method: 'POST',
- url: ENDPOINT,
+ url,
data: JSON.stringify(data),
options: {},
- }
+ };
},
/**
- * Parse response from Glimpse server
- * @param bidResponse {ServerResponse}
+ * Parse http response
+ * @param response {ServerResponse}
* @returns {Bid[]}
*/
- interpretResponse: (bidResponse) => {
- const isValidResponse = isValidBidResponse(bidResponse)
-
- if (isValidResponse) {
- const {auth, data} = bidResponse.body
- setVaultJwt(auth)
- return data.bids
+ interpretResponse: (response) => {
+ if (isValidResponse(response)) {
+ const { auth, data } = response.body;
+ setVaultJwt(auth);
+ const bids = data.bids.map(processBidResponse);
+ return bids;
}
-
- return []
+ return [];
},
-}
+};
function setVaultJwt(auth) {
- storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth)
+ storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth);
}
function getVaultJwt() {
- return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || ''
+ return (
+ storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || ''
+ );
}
function getReferer(bidderRequest) {
- const hasReferer =
- hasValue(bidderRequest) &&
- hasValue(bidderRequest.refererInfo) &&
- hasStringValue(bidderRequest.refererInfo.referer)
-
- if (hasReferer) {
- return bidderRequest.refererInfo.referer
- }
-
- return ''
+ return bidderRequest?.refererInfo?.referer || '';
}
-function getGdprConsentChoice(bidderRequest) {
- const hasGdprConsent =
- hasValue(bidderRequest) &&
- hasValue(bidderRequest.gdprConsent)
+function buildQuery(bidderRequest) {
+ let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$');
- if (hasGdprConsent) {
- const gdprConsent = bidderRequest.gdprConsent
- const hasGdprApplies = hasBooleanValue(gdprConsent.gdprApplies)
+ const timeout = config.getConfig('bidderTimeout');
+ url = appendQueryParam(url, 'tmax', timeout);
- return {
- consentString: gdprConsent.consentString || '',
- vendorData: gdprConsent.vendorData || {},
- gdprApplies: hasGdprApplies ? gdprConsent.gdprApplies : true,
- }
+ if (gdprApplies(bidderRequest)) {
+ const consentString = bidderRequest.gdprConsent.consentString;
+ url = appendQueryParam(url, 'gdpr', consentString);
}
- return {
- consentString: '',
- vendorData: {},
- gdprApplies: false,
+ if (ccpaApplies(bidderRequest)) {
+ url = appendQueryParam(url, 'ccpa', bidderRequest.uspConsent);
+ }
+
+ return url;
+}
+
+function appendQueryParam(url, key, value) {
+ if (!value) {
+ return url;
}
+ const prefix = url.includes('?') ? '&' : '?';
+ return `${url}${prefix}${key}=${encodeURIComponent(value)}`;
+}
+
+function gdprApplies(bidderRequest) {
+ return Boolean(bidderRequest?.gdprConsent?.gdprApplies);
}
-function processBidRequest(bidRequest) {
- const demand = bidRequest.params.demand || 'glimpse'
- const sizes = normalizeSizes(bidRequest.sizes)
- const keywords = bidRequest.params.keywords || {}
+function ccpaApplies(bidderRequest) {
+ return (
+ isStr(bidderRequest.uspConsent) &&
+ !isEmptyStr(bidderRequest.uspConsent) &&
+ bidderRequest.uspConsent?.substr(1, 3) !== '---'
+ );
+}
+
+function processBidRequest(bid) {
+ const sizes = normalizeSizes(bid.sizes);
return {
- demand,
+ bid: bid.bidId,
+ pid: bid.params.pid,
sizes,
- keywords,
- bidId: bidRequest.bidId,
- placementId: bidRequest.params.placementId,
- unitCode: bidRequest.adUnitCode,
- }
+ };
}
function normalizeSizes(sizes) {
@@ -148,84 +151,51 @@ function normalizeSizes(sizes) {
isArray(sizes) &&
sizes.length === 2 &&
!isArray(sizes[0]) &&
- !isArray(sizes[1])
+ !isArray(sizes[1]);
if (isSingleSize) {
- return [sizes]
+ return [sizes];
}
- return sizes
+ return sizes;
}
function getFirstPartyData() {
- const siteKeywords = parseGlobalKeywords('site')
- const userKeywords = parseGlobalKeywords('user')
-
- const siteAttributes = getConfig('ortb2.site.ext.data', {})
- const userAttributes = getConfig('ortb2.user.ext.data', {})
-
- return {
- site: {
- keywords: siteKeywords,
- attributes: siteAttributes,
- },
- user: {
- keywords: userKeywords,
- attributes: userAttributes,
- },
- }
+ let fpd = config.getConfig('ortb2') || {};
+ optimizeObject(fpd);
+ return fpd;
}
-function parseGlobalKeywords(scope) {
- const keywords = getConfig(`ortb2.${scope}.keywords`, '')
-
- return keywords
- .split(', ')
- .filter((keyword) => keyword !== '')
-}
-
-function getConfig(path, defaultValue) {
- return config.getConfig(path) || defaultValue
-}
-
-function isValidBidResponse(bidResponse) {
- return (
- hasValue(bidResponse) &&
- hasValue(bidResponse.body) &&
- hasValue(bidResponse.body.data) &&
- hasArrayValue(bidResponse.body.data.bids) &&
- hasStringValue(bidResponse.body.auth)
- )
+function optimizeObject(obj) {
+ if (!isPlainObject(obj)) {
+ return;
+ }
+ for (const [key, value] of Object.entries(obj)) {
+ optimizeObject(value);
+ // only delete empty object, array, or string
+ if (
+ (isPlainObject(value) || isArray(value) || isStr(value)) &&
+ isEmpty(value)
+ ) {
+ delete obj[key];
+ }
+ }
}
-function hasValue(value) {
- return (
- value !== undefined &&
- value !== null
- )
+function isValidResponse(bidResponse) {
+ const auth = bidResponse?.body?.auth;
+ const bids = bidResponse?.body?.data?.bids;
+ return isStr(auth) && isArray(bids) && !isEmpty(bids);
}
-function hasBooleanValue(value) {
- return (
- hasValue(value) &&
- typeof value === 'boolean'
- )
-}
+function processBidResponse(bid) {
+ const meta = bid.meta || {};
+ meta.advertiserDomains = bid.meta?.advertiserDomains || [];
-function hasStringValue(value) {
- return (
- hasValue(value) &&
- typeof value === 'string' &&
- value.length > 0
- )
-}
-
-function hasArrayValue(value) {
- return (
- hasValue(value) &&
- isArray(value) &&
- value.length > 0
- )
+ return {
+ ...bid,
+ meta,
+ };
}
-registerBidder(spec)
+registerBidder(spec);
diff --git a/modules/glimpseBidAdapter.md b/modules/glimpseBidAdapter.md
index 767efcecf54..e82c5d8f32e 100644
--- a/modules/glimpseBidAdapter.md
+++ b/modules/glimpseBidAdapter.md
@@ -24,15 +24,14 @@ const adUnits = [
sizes: [[300, 250]],
},
},
- bids: [{
- bidder: 'glimpse',
- params: {
- placementId: 'e53a7f564f8f44cc913b',
- keywords: {
- country: 'uk',
+ bids: [
+ {
+ bidder: 'glimpse',
+ params: {
+ pid: 'e53a7f564f8f44cc913b',
},
},
- }],
+ ],
},
-]
+];
```
diff --git a/modules/glomexBidAdapter.js b/modules/glomexBidAdapter.js
index 617a1a3d721..5cabd2515a9 100644
--- a/modules/glomexBidAdapter.js
+++ b/modules/glomexBidAdapter.js
@@ -1,6 +1,6 @@
-import { registerBidder } from '../src/adapters/bidderFactory.js'
-import find from 'core-js-pure/features/array/find.js'
-import { BANNER } from '../src/mediaTypes.js'
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {find} from '../src/polyfill.js';
+import {BANNER} from '../src/mediaTypes.js';
const ENDPOINT = 'https://prebid.mes.glomex.cloud/request-bid'
const BIDDER_CODE = 'glomex'
diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js
index fac19896177..087f74906fb 100644
--- a/modules/gmosspBidAdapter.js
+++ b/modules/gmosspBidAdapter.js
@@ -1,12 +1,9 @@
-import { getDNT, getBidIdParameter, tryAppendQueryString, isEmpty, createTrackPixelHtml, logError, deepSetValue } from '../src/utils.js';
+import { deepAccess, getDNT, getBidIdParameter, tryAppendQueryString, isEmpty, createTrackPixelHtml, logError, deepSetValue, getWindowTop, getWindowLocation } from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
import { BANNER } from '../src/mediaTypes.js';
-import { getStorageManager } from '../src/storageManager.js';
-
const BIDDER_CODE = 'gmossp';
const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad';
-const storage = getStorageManager();
export const spec = {
code: BIDDER_CODE,
@@ -34,7 +31,6 @@ export const spec = {
const urlInfo = getUrlInfo(bidderRequest.refererInfo);
const cur = getCurrencyType();
const dnt = getDNT() ? '1' : '0';
- const imuid = storage.getCookie('_im_uid.1000283') || '';
for (let i = 0; i < validBidRequests.length; i++) {
let queryString = '';
@@ -42,6 +38,9 @@ export const spec = {
const request = validBidRequests[i];
const tid = request.transactionId;
const bid = request.bidId;
+ const imuid = deepAccess(request, 'userId.imuid');
+ const sharedId = deepAccess(request, 'userId.pubcid');
+ const idlEnv = deepAccess(request, 'userId.idl_env');
const ver = '$prebid.version$';
const sid = getBidIdParameter('sid', request.params);
@@ -50,7 +49,10 @@ export const spec = {
queryString = tryAppendQueryString(queryString, 'ver', ver);
queryString = tryAppendQueryString(queryString, 'sid', sid);
queryString = tryAppendQueryString(queryString, 'im_uid', imuid);
+ queryString = tryAppendQueryString(queryString, 'shared_id', sharedId);
+ queryString = tryAppendQueryString(queryString, 'idl_env', idlEnv);
queryString = tryAppendQueryString(queryString, 'url', urlInfo.url);
+ queryString = tryAppendQueryString(queryString, 'meta_url', urlInfo.canonicalLink);
queryString = tryAppendQueryString(queryString, 'ref', urlInfo.ref);
queryString = tryAppendQueryString(queryString, 'cur', cur);
queryString = tryAppendQueryString(queryString, 'dnt', dnt);
@@ -112,7 +114,7 @@ export const spec = {
* @param {ServerResponse[]} serverResponses List of server's responses.
* @return {UserSync[]} The user syncs which should be dropped.
*/
- getUserSyncs: function(syncOptions, serverResponses) {
+ getUserSyncs: function (syncOptions, serverResponses) {
const syncs = [];
if (!serverResponses.length) {
return syncs;
@@ -141,27 +143,47 @@ function getCurrencyType() {
}
function getUrlInfo(refererInfo) {
+ let canonicalLink = refererInfo.canonicalUrl;
+
+ if (!canonicalLink) {
+ let metaElements = getMetaElements();
+ for (let i = 0; i < metaElements.length && !canonicalLink; i++) {
+ if (metaElements[i].getAttribute('property') == 'og:url') {
+ canonicalLink = metaElements[i].content;
+ }
+ }
+ }
+
return {
url: getUrl(refererInfo),
+ canonicalLink: canonicalLink,
ref: getReferrer(),
};
}
+function getMetaElements() {
+ try {
+ return getWindowTop.document.getElementsByTagName('meta');
+ } catch (e) {
+ return document.getElementsByTagName('meta');
+ }
+}
+
function getUrl(refererInfo) {
if (refererInfo && refererInfo.referer) {
return refererInfo.referer;
}
try {
- return window.top.location.href;
+ return getWindowTop.location.href;
} catch (e) {
- return window.location.href;
+ return getWindowLocation.href;
}
}
function getReferrer() {
try {
- return window.top.document.referrer;
+ return getWindowTop.document.referrer;
} catch (e) {
return document.referrer;
}
diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js
index 8b0e953b2b6..274e8db2b50 100644
--- a/modules/gnetBidAdapter.js
+++ b/modules/gnetBidAdapter.js
@@ -1,9 +1,13 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { _each, isEmpty, parseSizesInput } from '../src/utils.js';
import { BANNER } from '../src/mediaTypes.js';
+import { getStorageManager } from '../src/storageManager.js';
+import {ajax} from '../src/ajax.js';
+
+const storage = getStorageManager();
const BIDDER_CODE = 'gnet';
-const ENDPOINT = 'https://service.gnetrtb.com/api/adrequest';
+const ENDPOINT = 'https://service.gnetrtb.com/api';
export const spec = {
code: BIDDER_CODE,
@@ -36,6 +40,7 @@ export const spec = {
data.adUnitCode = request.adUnitCode;
data.bidId = request.bidId;
data.transactionId = request.transactionId;
+ data.gftuid = _getCookie();
data.sizes = parseSizesInput(request.sizes);
@@ -45,8 +50,7 @@ export const spec = {
bidRequests.push({
method: 'POST',
- url: ENDPOINT,
- mode: 'no-cors',
+ url: ENDPOINT + '/adrequest',
options: {
withCredentials: false,
},
@@ -99,6 +103,18 @@ export const spec = {
return [];
},
+
+ onBidWon: function (bid) {
+ ajax(ENDPOINT + '/bid-won', null, JSON.stringify(bid), {
+ method: 'POST',
+ });
+
+ return true;
+ },
};
+function _getCookie() {
+ return storage.cookiesAreEnabled() ? storage.getCookie('gftuid') : null;
+}
+
registerBidder(spec);
diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js
index 8057925a62c..46ae3054188 100644
--- a/modules/goldbachBidAdapter.js
+++ b/modules/goldbachBidAdapter.js
@@ -1,35 +1,34 @@
-import { Renderer } from '../src/Renderer.js';
+import {Renderer} from '../src/Renderer.js';
import {
- isEmpty,
+ chunk,
convertCamelToUnderscore,
- isFn,
- createTrackPixelHtml,
convertTypes,
+ createTrackPixelHtml,
+ deepAccess,
deepClone,
fill,
- getParameterByName,
+ getBidRequest,
getMaxValueFromArray,
getMinValueFromArray,
- chunk,
+ getParameterByName,
isArray,
isArrayOfNums,
+ isEmpty,
+ isFn,
isNumber,
- isStr,
isPlainObject,
+ isStr,
logError,
logInfo,
logMessage,
- deepAccess,
- getBidRequest,
transformBidderParamKeywords
} from '../src/utils.js';
-import { config } from '../src/config.js';
-import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js';
-import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js';
-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 { OUTSTREAM, INSTREAM } from '../src/video.js';
+import {config} from '../src/config.js';
+import {getIabSubCategory, registerBidder} from '../src/adapters/bidderFactory.js';
+import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {auctionManager} from '../src/auctionManager.js';
+import {find, includes} from '../src/polyfill.js';
+import {INSTREAM, OUTSTREAM} from '../src/video.js';
const BIDDER_CODE = 'goldbach';
const URL = 'https://ib.adnxs.com/ut/v3/prebid';
@@ -89,9 +88,11 @@ const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/appnexus-mapping/ma
const SCRIPT_TAG_START = '`;
- bid.mediaType = BANNER;
- }
- // Common properties
- bid.cpm = parseFloat(bidObject.price);
- bid.creativeId = bidObject.crid;
- bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD';
-
- // Deal ID. Composite ads can have multiple line items and the ID of the first
- // dealID line item will be used.
- if (isNumber(bidObject.lid) && bidObject.buying_type && bidObject.buying_type !== 'rtb') {
- bid.dealId = bidObject.lid;
- } else if (Array.isArray(bidObject.lid) &&
- Array.isArray(bidObject.buying_type) &&
- bidObject.lid.length === bidObject.buying_type.length) {
- let isDeal = false;
- bidObject.buying_type.forEach((bt, i) => {
- if (isDeal) return;
- if (bt && bt !== 'rtb') {
- isDeal = true;
- bid.dealId = bidObject.lid[i];
- }
- });
- }
+ serverResponse.body.seatbid.forEach(seatbid => {
+ if (!Array.isArray(seatbid.bid)) return;
- bid.height = bidObject.h;
- bid.netRevenue = bidObject.isNet ? bidObject.isNet : false;
- bid.requestId = bidObject.id;
- bid.ttl = 300;
- bid.width = bidObject.w;
+ seatbid.bid.forEach(bidObject => {
+ if (!bidObject.adm || !bidObject.price || bidObject.hasOwnProperty('errorCode')) {
+ return;
+ }
+ const bidRequest = getBidRequest(bidObject.impid, [bidderRequest]);
+ const idExt = deepAccess(bidObject, `ext.${BIDDER_CODE}`, {});
+
+ const bid = {
+ requestId: bidObject.impid,
+ cpm: bidObject.price,
+ creativeId: bidObject.crid,
+ currency: serverResponse.body.cur.toUpperCase() || 'USD',
+ dealId: (typeof idExt.buying_type === 'string' && idExt.buying_type !== 'rtb') ? idExt.line_item_id : undefined,
+ meta: {
+ advertiserDomains: bidObject.adomain ? bidObject.adomain : []
+ },
+ netRevenue: idExt.is_net || false,
+ ttl: CREATIVE_TTL
+ }
- if (!bid.width || !bid.height) {
- bid.width = 1;
- bid.height = 1;
- }
+ ID_RESPONSE.buildAd(bid, bidRequest, bidObject);
- if (bidObject.adomain) {
- bid.meta = {
- advertiserDomains: bidObject.adomain
- };
- }
+ ID_RAZR.addBidData({
+ bidRequest,
+ bid
+ });
- bids.push(bid);
+ bids.push(bid);
+ });
});
+
return bids;
},
@@ -209,549 +216,452 @@ export const spec = {
* @param {ServerResponse[]} serverResponses List of server's responses.
* @return {UserSync[]} The user syncs which should be dropped.
*/
- getUserSyncs: function(syncOptions, serverResponses) {
- if (syncOptions.pixelEnabled) {
- const syncs = [];
+ getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ if (config.getConfig('coppa') === true || !ID_UTIL.hasPurpose1Consent(gdprConsent)) {
+ return [];
+ }
+
+ const syncs = [];
+ if ((this.syncStore.extendMode || !syncOptions.pixelEnabled) && syncOptions.iframeEnabled) {
+ const { gdprApplies, consentString } = gdprConsent || {};
+ syncs.push({
+ type: 'iframe',
+ url: IFRAME_SYNC_URL +
+ `?placement_id=${this.syncStore.placementId}` +
+ (this.syncStore.extendMode ? '&pbs=1' : '') +
+ (typeof gdprApplies === 'boolean' ? `&gdpr=${Number(gdprApplies)}` : '') +
+ (consentString ? `&gdpr_consent=${consentString}` : '') +
+ (uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '')
+ });
+ } else if (syncOptions.pixelEnabled) {
serverResponses.forEach(response => {
- response.body.bid.forEach(bidObject => {
- if (isArray(bidObject.sync)) {
- bidObject.sync.forEach(syncElement => {
- if (syncs.indexOf(syncElement) === -1) {
- syncs.push(syncElement);
- }
- });
+ const syncArr = deepAccess(response, `body.ext.${BIDDER_CODE}.sync`, []);
+ syncArr.forEach(url => {
+ if (!syncs.some(sync => sync.url === url)) {
+ syncs.push({ type: 'image', url });
}
});
});
- return syncs.map(sync => ({ type: 'image', url: sync }));
}
- return [];
+
+ return syncs;
}
};
-function isInstreamVideo(bid) {
- const mediaTypes = Object.keys(deepAccess(bid, 'mediaTypes', {}));
- const videoMediaType = deepAccess(bid, 'mediaTypes.video');
- const context = deepAccess(bid, 'mediaTypes.video.context');
- return bid.mediaType === 'video' || (mediaTypes.length === 1 && videoMediaType && context !== 'outstream');
-}
-
-function isOutstreamVideo(bid) {
- const videoMediaType = deepAccess(bid, 'mediaTypes.video');
- const context = deepAccess(bid, 'mediaTypes.video.context');
- return videoMediaType && context === 'outstream';
-}
-
-function getVideoTargetingParams(bid) {
- const result = {};
- Object.keys(Object(bid.mediaTypes.video))
- .filter(key => includes(VIDEO_TARGETING, key))
- .forEach(key => {
- result[ key ] = bid.mediaTypes.video[ key ];
- });
- Object.keys(Object(bid.params.video))
- .filter(key => includes(VIDEO_TARGETING, key))
- .forEach(key => {
- result[ key ] = bid.params.video[ key ];
+registerBidder(spec);
+
+const ID_REQUEST = {
+ buildServerRequests(basicRequest, bidRequests, bidderRequest) {
+ const globalExtendMode = config.getConfig('improvedigital.extend') === true;
+ const requests = [];
+ const singleRequestMode = config.getConfig('improvedigital.singleRequest') === true;
+
+ const extendImps = [];
+ const adServerImps = [];
+
+ function formatRequest(imps, transactionId, extendMode) {
+ const request = deepClone(basicRequest);
+ request.imp = imps;
+ request.id = getUniqueIdentifierStr();
+ if (transactionId) {
+ deepSetValue(request, 'source.tid', transactionId);
+ }
+ return {
+ method: 'POST',
+ url: extendMode ? EXTEND_URL : AD_SERVER_URL,
+ data: JSON.stringify(request),
+ bidderRequest
+ }
+ };
+
+ bidRequests.map((bidRequest) => {
+ const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidRequest.params);
+ const imp = this.buildImp(bidRequest, extendModeEnabled);
+ if (singleRequestMode) {
+ extendModeEnabled ? extendImps.push(imp) : adServerImps.push(imp);
+ } else {
+ requests.push(formatRequest([imp], bidRequest.transactionId, extendModeEnabled));
+ }
});
- return result;
-}
-function getBidFloor(bid) {
- if (!isFn(bid.getFloor)) {
- return null;
- }
- const floor = bid.getFloor({
- currency: 'USD',
- mediaType: '*',
- size: '*'
- });
- if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
- return floor.floor;
- }
- return null;
-}
-
-function outstreamRender(bid) {
- bid.renderer.push(() => {
- window.ANOutstreamVideo.renderAd({
- sizes: [bid.width, bid.height],
- targetId: bid.adUnitCode,
- adResponse: bid.adResponse,
- rendererOptions: bid.renderer.getConfig()
- }, handleOutstreamRendererEvents.bind(null, bid));
- });
-}
-
-function handleOutstreamRendererEvents(bid, id, eventName) {
- bid.renderer.handleVideoEvent({ id, eventName });
-}
-
-function createRenderer(bidRequest) {
- const renderer = Renderer.install({
- id: bidRequest.adUnitCode,
- url: RENDERER_URL,
- loaded: false,
- config: deepAccess(bidRequest, 'renderer.options'),
- adUnitCode: bidRequest.adUnitCode
- });
- try {
- renderer.setRender(outstreamRender);
- } catch (err) {
- logWarn('Prebid Error calling setRender on renderer', err);
- }
- return renderer;
-}
-
-function getNormalizedBidRequest(bid) {
- let adUnitId = getBidIdParameter('adUnitCode', bid) || null;
- let placementId = getBidIdParameter('placementId', bid.params) || null;
- let publisherId = null;
- let placementKey = null;
-
- if (placementId === null) {
- publisherId = getBidIdParameter('publisherId', bid.params) || null;
- placementKey = getBidIdParameter('placementKey', bid.params) || null;
- }
- const keyValues = getBidIdParameter('keyValues', bid.params) || null;
- const singleSizeFilter = getBidIdParameter('size', bid.params) || null;
- const bidId = getBidIdParameter('bidId', bid);
- const transactionId = getBidIdParameter('transactionId', bid);
- const currency = config.getConfig('currency.adServerCurrency');
-
- let normalizedBidRequest = {};
- if (isInstreamVideo(bid)) {
- normalizedBidRequest.adTypes = [ VIDEO ];
- }
- if (isInstreamVideo(bid) || isOutstreamVideo(bid)) {
- normalizedBidRequest.video = getVideoTargetingParams(bid);
- }
- if (placementId) {
- normalizedBidRequest.placementId = placementId;
- } else {
- if (publisherId) {
- normalizedBidRequest.publisherId = publisherId;
+ if (!singleRequestMode) {
+ return requests;
}
- if (placementKey) {
- normalizedBidRequest.placementKey = placementKey;
+ // In the single request mode, split imps between those going to the ad server and those going to extend server
+ if (extendImps.length) {
+ requests.push(formatRequest(extendImps, null, true));
+ }
+ if (adServerImps.length) {
+ requests.push(formatRequest(adServerImps, null, false));
}
- }
-
- if (keyValues) {
- normalizedBidRequest.keyValues = keyValues;
- }
-
- if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && !isOutstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) {
- normalizedBidRequest.format = bid.sizes;
- } else if (singleSizeFilter && singleSizeFilter.w && singleSizeFilter.h) {
- normalizedBidRequest.size = {};
- normalizedBidRequest.size.h = singleSizeFilter.h;
- normalizedBidRequest.size.w = singleSizeFilter.w;
- }
- if (bidId) {
- normalizedBidRequest.id = bidId;
- }
- if (adUnitId) {
- normalizedBidRequest.adUnitId = adUnitId;
- }
- if (transactionId) {
- normalizedBidRequest.transactionId = transactionId;
- }
- if (currency) {
- normalizedBidRequest.currency = currency;
- }
- // Floor
- let bidFloor = getBidFloor(bid);
- let bidFloorCur = null;
- if (!bidFloor) {
- bidFloor = getBidIdParameter('bidFloor', bid.params);
- bidFloorCur = getBidIdParameter('bidFloorCur', bid.params);
- }
- if (bidFloor) {
- normalizedBidRequest.bidFloor = bidFloor;
- normalizedBidRequest.bidFloorCur = bidFloorCur ? bidFloorCur.toUpperCase() : 'USD';
- }
- return normalizedBidRequest;
-}
+ return requests;
+ },
-function getNormalizedNativeAd(rawNative) {
- const native = {};
- if (!rawNative || !isArray(rawNative.assets)) {
- return null;
- }
- // Assets
- rawNative.assets.forEach(asset => {
- if (asset.title) {
- native.title = asset.title.text;
- } else if (asset.data) {
- switch (asset.data.type) {
- case 1:
- native.sponsoredBy = asset.data.value;
- break;
- case 2:
- native.body = asset.data.value;
- break;
- case 3:
- native.rating = asset.data.value;
- break;
- case 4:
- native.likes = asset.data.value;
- break;
- case 5:
- native.downloads = asset.data.value;
- break;
- case 6:
- native.price = asset.data.value;
- break;
- case 7:
- native.salePrice = asset.data.value;
- break;
- case 8:
- native.phone = asset.data.value;
- break;
- case 9:
- native.address = asset.data.value;
- break;
- case 10:
- native.body2 = asset.data.value;
- break;
- case 11:
- native.displayUrl = asset.data.value;
- break;
- case 12:
- native.cta = asset.data.value;
- break;
- }
- } else if (asset.img) {
- switch (asset.img.type) {
- case 2:
- native.icon = {
- url: asset.img.url,
- width: asset.img.w,
- height: asset.img.h
- };
- break;
- case 3:
- native.image = {
- url: asset.img.url,
- width: asset.img.w,
- height: asset.img.h
- };
- break;
- }
+ isExtendModeEnabled(globalExtendMode, bidParams) {
+ const extendMode = typeof bidParams.extend === 'boolean' ? bidParams.extend : globalExtendMode;
+ if (extendMode && !spec.syncStore.extendMode) {
+ spec.syncStore.extendMode = true;
}
- });
- // Trackers
- if (rawNative.eventtrackers) {
- native.impressionTrackers = [];
- rawNative.eventtrackers.forEach(tracker => {
- // Only handle impression event. Viewability events are not supported yet.
- if (tracker.event !== 1) return;
- switch (tracker.method) {
- case 1: // img
- native.impressionTrackers.push(tracker.url);
- break;
- case 2: // js
- // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used.
- native.javascriptTrackers = ``;
- break;
- }
- });
- } else {
- native.impressionTrackers = rawNative.imptrackers || [];
- native.javascriptTrackers = rawNative.jstracker;
- }
- if (rawNative.link) {
- native.clickUrl = rawNative.link.url;
- native.clickTrackers = rawNative.link.clicktrackers;
- }
- if (rawNative.privacy) {
- native.privacyLink = rawNative.privacy;
- }
- return native;
-}
-registerBidder(spec);
+ return extendMode;
+ },
-export function ImproveDigitalAdServerJSClient(endPoint) {
- this.CONSTANTS = {
- AD_SERVER_BASE_URL: 'ice.360yield.com',
- END_POINT: endPoint || 'hb',
- AD_SERVER_URL_PARAM: 'jsonp=',
- CLIENT_VERSION: 'JS-6.4.0',
- MAX_URL_LENGTH: 2083,
- ERROR_CODES: {
- MISSING_PLACEMENT_PARAMS: 2,
- LIB_VERSION_MISSING: 3
- },
- RETURN_OBJ_TYPE: {
- DEFAULT: 0,
- URL_PARAMS_SPLIT: 1
- }
- };
-
- this.getErrorReturn = function(errorCode) {
- return {
- idMappings: {},
- requests: {},
- 'errorCode': errorCode
+ buildImp(bidRequest, extendMode) {
+ const imp = {
+ id: getBidIdParameter('bidId', bidRequest) || getUniqueIdentifierStr(),
+ secure: Number(window.location.protocol === 'https:'),
};
- };
- this.createRequest = function(requestObject, requestParameters, extraRequestParameters) {
- if (!requestParameters.libVersion) {
- return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.LIB_VERSION_MISSING);
+ // Floor
+ const bidFloor = this.getBidFloor(bidRequest) || getBidIdParameter('bidFloor', bidRequest.params);
+ if (bidFloor) {
+ const bidFloorCur = getBidIdParameter('bidFloorCur', bidRequest.params) || 'USD';
+ deepSetValue(imp, 'bidfloor', bidFloor);
+ deepSetValue(imp, 'bidfloorcur', bidFloorCur ? bidFloorCur.toUpperCase() : undefined);
}
- requestParameters.returnObjType = requestParameters.returnObjType || this.CONSTANTS.RETURN_OBJ_TYPE.DEFAULT;
- requestParameters.adServerBaseUrl = 'https://' + (requestParameters.adServerBaseUrl || this.CONSTANTS.AD_SERVER_BASE_URL);
-
- let impressionObjects = [];
- let impressionObject;
- if (isArray(requestObject)) {
- for (let counter = 0; counter < requestObject.length; counter++) {
- impressionObject = this.createImpressionObject(requestObject[counter]);
- impressionObjects.push(impressionObject);
+ const bidderParamsPath = extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder';
+ const placementId = getBidIdParameter('placementId', bidRequest.params);
+ if (placementId) {
+ deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId);
+ if (extendMode) {
+ deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId);
}
} else {
- impressionObject = this.createImpressionObject(requestObject);
- impressionObjects.push(impressionObject);
+ deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params));
+ deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params));
}
- let returnIdMappings = true;
- if (requestParameters.returnObjType === this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT) {
- returnIdMappings = false;
- }
+ deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined);
- let returnObject = {};
- returnObject.requests = [];
- if (returnIdMappings) {
- returnObject.idMappings = [];
+ // Adding GPID
+ const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') ||
+ deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot') ||
+ deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot');
+
+ deepSetValue(imp, 'ext.gpid', gpid);
+
+ // Adding Interstitial Signal
+ if (deepAccess(bidRequest, 'ortb2Imp.instl')) {
+ imp.instl = 1;
}
- let errors = null;
- let baseUrl = `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`;
+ const videoParams = deepAccess(bidRequest, 'mediaTypes.video');
+ if (videoParams) {
+ imp.video = this.buildVideoRequest(bidRequest);
+ deepSetValue(imp, 'ext.is_rewarded_inventory', (videoParams.rewarded === 1 || deepAccess(videoParams, 'ext.rewarded') === 1) || undefined);
+ }
- let bidRequestObject = {
- bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters)
- };
- for (let counter = 0; counter < impressionObjects.length; counter++) {
- impressionObject = impressionObjects[counter];
-
- if (impressionObject.errorCode) {
- errors = errors || [];
- errors.push({
- errorCode: impressionObject.errorCode,
- adUnitId: impressionObject.adUnitId
- });
- } else {
- if (returnIdMappings) {
- returnObject.idMappings.push({
- adUnitId: impressionObject.adUnitId,
- id: impressionObject.impressionObject.id
- });
- }
- bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || [];
- bidRequestObject.bid_request.imp.push(impressionObject.impressionObject);
-
- let writeLongRequest = false;
- const outputUri = baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject));
- if (outputUri.length > this.CONSTANTS.MAX_URL_LENGTH) {
- writeLongRequest = true;
- if (bidRequestObject.bid_request.imp.length > 1) {
- // Pop the current request and process it again in the next iteration
- bidRequestObject.bid_request.imp.pop();
- if (returnIdMappings) {
- returnObject.idMappings.pop();
- }
- counter--;
- }
- }
+ if (deepAccess(bidRequest, 'mediaTypes.banner')) {
+ imp.banner = this.buildBannerRequest(bidRequest);
+ }
- if (writeLongRequest ||
- !requestParameters.singleRequestMode ||
- counter === impressionObjects.length - 1) {
- returnObject.requests.push(this.formatRequest(requestParameters, bidRequestObject));
- bidRequestObject = {
- bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters)
- };
- }
+ if (deepAccess(bidRequest, 'mediaTypes.native')) {
+ const nativeImp = this.buildNativeRequest(bidRequest);
+ if (nativeImp) {
+ imp.native = nativeImp;
}
}
- if (errors) {
- returnObject.errors = errors;
- }
+ return imp;
+ },
- return returnObject;
- };
+ buildVideoRequest(bidRequest) {
+ const videoParams = deepClone(bidRequest.mediaTypes.video);
+ const videoImproveParams = deepClone(deepAccess(bidRequest, 'params.video', {}));
+ const video = {...videoParams, ...videoImproveParams};
- this.formatRequest = function(requestParameters, bidRequestObject) {
- switch (requestParameters.returnObjType) {
- case this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT:
- return {
- method: 'GET',
- url: `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}`,
- data: `${this.CONSTANTS.AD_SERVER_URL_PARAM}${encodeURIComponent(JSON.stringify(bidRequestObject))}`
- };
- default:
- const baseUrl = `${requestParameters.adServerBaseUrl}/` +
- `${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`;
- return {
- url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject))
- }
+ if (Array.isArray(video.playerSize)) {
+ // Player size can be defined as [w, h] or [[w, h]]
+ const size = Array.isArray(video.playerSize[0]) ? video.playerSize[0] : video.playerSize;
+ video.w = size[0];
+ video.h = size[1];
}
- };
+ video.placement = this.isOutstreamVideo(bidRequest) ? VIDEO_PARAMS.PLACEMENT_TYPE.OUTSTREAM : VIDEO_PARAMS.PLACEMENT_TYPE.INSTREAM;
- this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) {
- let impressionBidRequestObject = {};
- impressionBidRequestObject.secure = 1;
- if (requestParameters.requestId) {
- impressionBidRequestObject.id = requestParameters.requestId;
- } else {
- impressionBidRequestObject.id = getUniqueIdentifierStr();
- }
- if (requestParameters.domain) {
- impressionBidRequestObject.domain = requestParameters.domain;
- }
- if (requestParameters.page) {
- impressionBidRequestObject.page = requestParameters.page;
- }
- if (requestParameters.ref) {
- impressionBidRequestObject.ref = requestParameters.ref;
- }
- if (requestParameters.callback) {
- impressionBidRequestObject.callback = requestParameters.callback;
- }
- if (requestParameters.libVersion) {
- impressionBidRequestObject.version = requestParameters.libVersion + '-' + this.CONSTANTS.CLIENT_VERSION;
- }
- if (requestParameters.referrer) {
- impressionBidRequestObject.referrer = requestParameters.referrer;
- }
- if (requestParameters.gdpr || requestParameters.gdpr === 0) {
- impressionBidRequestObject.gdpr = requestParameters.gdpr;
- }
- if (requestParameters.usPrivacy) {
- impressionBidRequestObject.us_privacy = requestParameters.usPrivacy;
- }
- if (requestParameters.schain) {
- impressionBidRequestObject.schain = requestParameters.schain;
- }
- if (requestParameters.pagecat) {
- impressionBidRequestObject.pagecat = requestParameters.pagecat;
- }
- if (requestParameters.genre) {
- impressionBidRequestObject.genre = requestParameters.genre;
- }
- if (requestParameters.user) {
- impressionBidRequestObject.user = requestParameters.user;
- }
- if (extraRequestParameters) {
- for (let prop in extraRequestParameters) {
- impressionBidRequestObject[prop] = extraRequestParameters[prop];
- }
+ // Mimes is required
+ if (!video.mimes) {
+ video.mimes = VIDEO_PARAMS.DEFAULT_MIMES;
}
- if (requestParameters.coppa) {
- impressionBidRequestObject.coppa = 1;
+ // skip must be 0 or 1
+ if (video.skip !== 1) {
+ delete video.skipmin;
+ delete video.skipafter;
+ if (video.skip !== 0) {
+ logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`);
+ delete video.skip;
+ }
}
- return impressionBidRequestObject;
- };
+ Object.keys(video).forEach(prop => {
+ if (VIDEO_PARAMS.SUPPORTED_PROPERTIES.indexOf(prop) === -1) delete video[prop];
+ });
+ return video;
+ },
- this.createImpressionObject = function(placementObject) {
- let outputObject = {};
- let impressionObject = {};
- outputObject.impressionObject = impressionObject;
+ buildBannerRequest(bidRequest) {
+ // Set the desired creative sizes
+ // Input Format: array of pairs, i.e. [[300, 250], [250, 250]]
+ // Unless improvedigital.usePrebidSizes == true, no sizes are sent to the server
+ // and the sizes defined in the server for the placement will be used
+ const banner = {};
+ if (config.getConfig('improvedigital.usePrebidSizes') === true && bidRequest.sizes) {
+ // Convert sizes from [x, y] to { w: x, h: y}
+ banner.format = bidRequest.sizes.map(sizePair => ({w: sizePair[0], h: sizePair[1]}));
+ }
+ return banner;
+ },
- if (placementObject.id) {
- impressionObject.id = placementObject.id;
- } else {
- impressionObject.id = getUniqueIdentifierStr();
- }
- if (placementObject.adTypes) {
- impressionObject.ad_types = placementObject.adTypes;
- }
- if (placementObject.adUnitId) {
- outputObject.adUnitId = placementObject.adUnitId;
- }
- if (placementObject.currency) {
- impressionObject.currency = placementObject.currency.toUpperCase();
- }
- if (placementObject.bidFloor) {
- impressionObject.bidfloor = placementObject.bidFloor;
- }
- if (placementObject.bidFloorCur) {
- impressionObject.bidfloorcur = placementObject.bidFloorCur.toUpperCase();
+ buildNativeRequest(bidRequest) {
+ const nativeParams = bidRequest.nativeParams;
+ if (!nativeParams) {
+ return null;
+ }
+ const request = {
+ assets: [],
+ }
+ for (let i of Object.keys(nativeParams)) {
+ const assetOrtbParams = NATIVE_DATA.ASSETS[i];
+ if (assetOrtbParams) {
+ const assetParams = nativeParams[i];
+ const asset = {
+ id: assetOrtbParams.id,
+ required: Number(assetParams.required),
+ };
+ switch (assetOrtbParams.assetType) {
+ case NATIVE_DATA.ASSET_TYPES.TITLE:
+ asset.title = {len: assetParams.len || assetOrtbParams.default.len};
+ break;
+ case NATIVE_DATA.ASSET_TYPES.DATA:
+ asset.data = cleanObj({type: assetOrtbParams.type, len: assetParams.len})
+ break;
+ case NATIVE_DATA.ASSET_TYPES.IMG:
+ asset.img = cleanObj({
+ type: assetOrtbParams.type,
+ w: deepAccess(assetParams, 'sizes.0'),
+ h: deepAccess(assetParams, 'sizes.1'),
+ wmin: deepAccess(assetParams, 'aspect_ratios.0.min_width'),
+ hmin: deepAccess(assetParams, 'aspect_ratios.0.min_height')
+ });
+ break;
+ default:
+ return;
+ }
+ request.assets.push(asset);
+ }
}
- if (placementObject.placementId) {
- impressionObject.pid = placementObject.placementId;
+ if (!request.assets.length) {
+ logWarn('No native assets recognized. Ignoring native ad request');
+ return null;
}
- if (placementObject.publisherId) {
- impressionObject.pubid = placementObject.publisherId;
+ return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) };
+ },
+
+ isOutstreamVideo(bidRequest) {
+ return deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream';
+ },
+
+ getBidFloor(bidRequest) {
+ if (!isFn(bidRequest.getFloor)) {
+ return null;
}
- if (placementObject.placementKey) {
- impressionObject.pkey = placementObject.placementKey;
+ const floor = bidRequest.getFloor({
+ currency: 'USD',
+ mediaType: '*',
+ size: '*'
+ });
+ if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
+ return floor.floor;
}
- if (placementObject.transactionId) {
- impressionObject.tid = placementObject.transactionId;
+ return null;
+ },
+
+ buildSiteOrApp(request, bidderRequest) {
+ const app = {};
+ const configAppSettings = config.getConfig('app') || {};
+ const fpdAppSettings = config.getConfig('ortb2.app') || {};
+ mergeDeep(app, configAppSettings, fpdAppSettings);
+
+ if (Object.keys(app).length !== 0) {
+ request.app = app;
+ } else {
+ const site = {};
+ const url = config.getConfig('pageUrl') || deepAccess(bidderRequest, 'refererInfo.referer');
+ if (url) {
+ site.page = url;
+ site.domain = parseUrl(url).hostname;
+ }
+ const configSiteSettings = config.getConfig('site') || {};
+ const fpdSiteSettings = config.getConfig('ortb2.site') || {};
+ mergeDeep(site, configSiteSettings, fpdSiteSettings);
+ request.site = site;
}
- if (!isEmpty(placementObject.video)) {
- const video = Object.assign({}, placementObject.video);
- // skip must be 0 or 1
- if (video.skip !== 1) {
- delete video.skipmin;
- delete video.skipafter;
- if (video.skip !== 0) {
- logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`);
- delete video.skip;
- }
+ },
+};
+
+const ID_RESPONSE = {
+ buildAd(bid, bidRequest, bidResponse) {
+ if (bidRequest.mediaTypes && Object.keys(bidRequest.mediaTypes).length === 1) {
+ if (deepAccess(bidRequest, 'mediaTypes.video')) {
+ this.buildVideoAd(bid, bidRequest, bidResponse);
+ } else if (deepAccess(bidRequest, 'mediaTypes.banner')) {
+ this.buildBannerAd(bid, bidRequest, bidResponse);
+ } else if (deepAccess(bidRequest, 'mediaTypes.native')) {
+ this.buildNativeAd(bid, bidRequest, bidResponse)
}
- if (!isEmpty(video)) {
- impressionObject.video = video;
+ } else {
+ // Detect media type for multi-format response
+ if (bidResponse.adm.search(/^(<\?xml| {
+ // Only handle impression event. Viewability events are not supported yet.
+ if (tracker.event !== 1) return;
+ switch (tracker.method) {
+ case 1: // img
+ nativeAd.impressionTrackers.push(tracker.url);
+ break;
+ case 2: // js
+ // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used.
+ nativeAd.javascriptTrackers = ``;
+ break;
}
+ });
+ } else {
+ nativeAd.impressionTrackers = nativeResponse.imptrackers || [];
+ nativeAd.javascriptTrackers = nativeResponse.jstracker;
+ }
+ nativeResponse.assets.map(asset => {
+ const assetParams = NATIVE_DATA.getAssetById(asset.id);
+ switch (assetParams.assetType) {
+ case NATIVE_DATA.ASSET_TYPES.TITLE:
+ nativeAd.title = asset.title.text;
+ break;
+ case NATIVE_DATA.ASSET_TYPES.DATA:
+ nativeAd[assetParams.name] = asset.data.value;
+ break;
+ case NATIVE_DATA.ASSET_TYPES.IMG:
+ nativeAd[assetParams.name] = {
+ url: asset.img.url,
+ width: asset.img.w,
+ height: asset.img.h,
+ };
+ break;
}
- }
+ });
+ bid.native = nativeAd;
+ },
+};
- impressionObject.banner = {};
- if (placementObject.size && placementObject.size.w && placementObject.size.h) {
- impressionObject.banner.w = placementObject.size.w;
- impressionObject.banner.h = placementObject.size.h;
+const ID_OUTSTREAM = {
+ RENDERER_URL: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js',
+ createRenderer(bidRequest) {
+ const renderer = Renderer.install({
+ id: bidRequest.adUnitCode,
+ url: this.RENDERER_URL,
+ config: deepAccess(bidRequest, 'renderer.options'),
+ adUnitCode: bidRequest.adUnitCode
+ });
+ try {
+ renderer.setRender(this.render);
+ } catch (err) {
+ logWarn('Prebid Error calling setRender on renderer', err);
}
+ return renderer;
+ },
- // Set of desired creative sizes
- // Input Format: array of pairs, i.e. [[300, 250], [250, 250]]
- if (placementObject.format && isArray(placementObject.format)) {
- const format = placementObject.format
- .filter(sizePair => sizePair.length === 2 &&
- isInteger(sizePair[0]) &&
- isInteger(sizePair[1]) &&
- sizePair[0] >= 0 &&
- sizePair[1] >= 0)
- .map(sizePair => {
- return { w: sizePair[0], h: sizePair[1] }
- });
- if (format.length > 0) {
- impressionObject.banner.format = format;
- }
+ render(bid) {
+ bid.renderer.push(() => {
+ window.ANOutstreamVideo.renderAd({
+ sizes: [bid.width, bid.height],
+ targetId: bid.adUnitCode,
+ adResponse: bid.adResponse,
+ rendererOptions: bid.renderer.getConfig()
+ }, ID_OUTSTREAM.handleRendererEvents.bind(null, bid));
+ });
+ },
+
+ handleRendererEvents(bid, id, eventName) {
+ bid.renderer.handleVideoEvent({ id, eventName });
+ },
+};
+
+const ID_RAZR = {
+ RENDERER_URL: 'https://razr.improvedigital.com/renderer.js',
+ addBidData({bid, bidRequest}) {
+ if (this.isValidBid(bid)) {
+ bid.renderer = Renderer.install({
+ url: this.RENDERER_URL,
+ config: {bidRequest}
+ });
+ bid.renderer.setRender(this.render);
}
+ },
- if (!impressionObject.pid &&
- !impressionObject.pubid &&
- !impressionObject.pkey &&
- !(impressionObject.banner && impressionObject.banner.w && impressionObject.banner.h)) {
- outputObject.impressionObject = null;
- outputObject.errorCode = this.CONSTANTS.ERROR_CODES.MISSING_PLACEMENT_PARAMS;
+ isValidBid(bid) {
+ return bid && /razr:\/\//.test(bid.ad);
+ },
+
+ render(bid) {
+ const {bidRequest} = bid.renderer.getConfig();
+
+ const payload = {
+ type: 'prebid',
+ bidRequest,
+ bid,
+ config: mergeDeep(
+ {},
+ config.getConfig('improvedigital.rendererConfig'),
+ deepAccess(bidRequest, 'params.rendererConfig')
+ )
+ };
+
+ const razr = window.razr = window.razr || {};
+ razr.queue = razr.queue || [];
+ razr.queue.push(payload);
+ }
+};
+
+const ID_UTIL = {
+ hasPurpose1Consent(gdprConsent) {
+ if (gdprConsent && gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) {
+ return (deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true);
}
- return outputObject;
- };
-}
+ return true;
+ }
+};
diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js
index ee7a481e7a2..c967f530e75 100644
--- a/modules/insticatorBidAdapter.js
+++ b/modules/insticatorBidAdapter.js
@@ -1,14 +1,9 @@
-import { config } from '../src/config.js';
-import { BANNER } from '../src/mediaTypes.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import {
- deepAccess,
- generateUUID,
- logError,
- isArray,
-} from '../src/utils.js';
-import { getStorageManager } from '../src/storageManager.js';
-import find from 'core-js-pure/features/array/find.js';
+import {config} from '../src/config.js';
+import {BANNER} from '../src/mediaTypes.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {deepAccess, generateUUID, logError, isArray} from '../src/utils.js';
+import {getStorageManager} from '../src/storageManager.js';
+import {find} from '../src/polyfill.js';
const BIDDER_CODE = 'insticator';
const ENDPOINT = 'https://ex.ingage.tech/v1/openrtb'; // production endpoint
@@ -17,7 +12,7 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days
const BID_TTL = 300; // 5 minutes
const GVLID = 910;
-export const storage = getStorageManager(GVLID, BIDDER_CODE);
+export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
config.setDefaults({
insticator: {
@@ -95,6 +90,7 @@ function buildImpression(bidRequest) {
}
function buildDevice() {
+ const deviceConfig = config.getConfig('device');
const device = {
w: window.innerWidth,
h: window.innerHeight,
@@ -105,8 +101,6 @@ function buildDevice() {
},
};
- const deviceConfig = config.getConfig('device');
-
if (typeof deviceConfig === 'object') {
Object.assign(device, deviceConfig);
}
diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js
index 321c3c4c1ab..d61fe624c59 100644
--- a/modules/integr8BidAdapter.js
+++ b/modules/integr8BidAdapter.js
@@ -10,7 +10,7 @@ const SIZE_SEPARATOR = ';';
const BISKO_ID = 'biskoId';
const STORAGE_ID = 'bisko-sid';
const SEGMENTS = 'biskoSegments';
-const storage = getStorageManager();
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js
index 2092f9a185a..1347fa04bd5 100644
--- a/modules/intentIqIdSystem.js
+++ b/modules/intentIqIdSystem.js
@@ -6,16 +6,17 @@
*/
import { logError, logInfo } from '../src/utils.js';
-import {ajax} from '../src/ajax.js';
-import {submodule} from '../src/hook.js'
-import {getStorageManager} from '../src/storageManager.js';
+import { ajax } from '../src/ajax.js';
+import { submodule } from '../src/hook.js'
+import { getStorageManager } from '../src/storageManager.js';
const PCID_EXPIRY = 365;
const MODULE_NAME = 'intentIqId';
export const FIRST_PARTY_KEY = '_iiq_fdata';
+export var FIRST_PARTY_DATA_KEY = '_iiq_fdata';
-export const storage = getStorageManager(undefined, MODULE_NAME);
+export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME });
const INVALID_ID = 'INVALID_ID';
@@ -117,6 +118,8 @@ export const intentIqIdSubmodule = {
logError('User ID - intentIqId submodule requires a valid partner to be defined');
return;
}
+ if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner }
+ let rrttStrtTime = 0;
// Read Intent IQ 1st party id or generate it if none exists
let firstPartyData = tryParse(readData(FIRST_PARTY_KEY));
@@ -126,12 +129,17 @@ export const intentIqIdSubmodule = {
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData));
}
+ let partnerData = tryParse(readData(FIRST_PARTY_DATA_KEY));
+ if (!partnerData) partnerData = {};
+
// use protocol relative urls for http or https
let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`;
url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : '';
url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : '';
url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : '';
url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : '';
+ url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : '';
+ url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : '';
const resp = function (callback) {
const callbacks = {
@@ -140,14 +148,30 @@ export const intentIqIdSubmodule = {
// If response is a valid json and should save is true
if (respJson && respJson.ls) {
// Store pid field if found in response json
+ let shouldUpdateLs = false;
if ('pid' in respJson) {
firstPartyData.pid = respJson.pid;
- storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData));
+ shouldUpdateLs = true;
+ }
+ if ('cttl' in respJson) {
+ partnerData.cttl = respJson.cttl;
+ shouldUpdateLs = true;
}
-
// If should save and data is empty, means we should save as INVALID_ID
if (respJson.data == '') {
respJson.data = INVALID_ID;
+ } else {
+ partnerData.data = respJson.data;
+ shouldUpdateLs = true;
+ }
+ if (rrttStrtTime && rrttStrtTime > 0) {
+ partnerData.rrtt = Date.now() - rrttStrtTime;
+ shouldUpdateLs = true;
+ }
+ if (shouldUpdateLs === true) {
+ partnerData.date = Date.now()
+ storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData));
+ storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData));
}
callback(respJson.data);
} else {
@@ -159,9 +183,13 @@ export const intentIqIdSubmodule = {
callback();
}
};
- ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true});
+ if (partnerData.date && partnerData.cttl && partnerData.data &&
+ Date.now() - partnerData.date < partnerData.cttl) { callback(partnerData.data); } else {
+ rrttStrtTime = Date.now();
+ ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true });
+ }
};
- return {callback: resp};
+ return { callback: resp };
}
};
diff --git a/modules/intersectionRtdProvider.js b/modules/intersectionRtdProvider.js
index 4404c4148fe..c7d03b25b57 100644
--- a/modules/intersectionRtdProvider.js
+++ b/modules/intersectionRtdProvider.js
@@ -2,8 +2,9 @@ import {submodule} from '../src/hook.js';
import {isFn, logError} from '../src/utils.js';
import {config} from '../src/config.js';
import {getGlobal} from '../src/prebidGlobal.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {includes} from '../src/polyfill.js';
import '../src/adapterManager.js';
+
let observerAvailable = true;
function getIntersectionData(requestBidsObject, onDone, providerConfig, userConsent) {
const intersectionMap = {};
diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js
index bc4387695c3..717a886a1f6 100644
--- a/modules/invibesBidAdapter.js
+++ b/modules/invibesBidAdapter.js
@@ -9,14 +9,14 @@ const CONSTANTS = {
SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync',
TIME_TO_LIVE: 300,
DEFAULT_CURRENCY: 'EUR',
- PREBID_VERSION: 7,
+ PREBID_VERSION: 8,
METHOD: 'GET',
INVIBES_VENDOR_ID: 436,
USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'],
META_TAXONOMY: ['networkId', 'networkName', 'agencyId', 'agencyName', 'advertiserId', 'advertiserName', 'advertiserDomains', 'brandId', 'brandName', 'primaryCatId', 'secondaryCatIds', 'mediaType']
};
-const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID);
+const storage = getStorageManager({gvlid: CONSTANTS.INVIBES_VENDOR_ID, bidderCode: CONSTANTS.BIDDER_CODE});
export const spec = {
code: CONSTANTS.BIDDER_CODE,
@@ -95,8 +95,6 @@ function buildRequest(bidRequests, bidderRequest) {
invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent);
invibes.visitId = invibes.visitId || generateRandomId();
- invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie');
- let lid = initDomainId(invibes.domainOptions);
const currentQueryStringParams = parseQueryStringParams();
let userIdModel = getUserIds(_userId);
@@ -113,7 +111,7 @@ function buildRequest(bidRequests, bidderRequest) {
location: getDocumentLocation(topWin),
videoAdHtmlId: generateRandomId(),
showFallback: currentQueryStringParams['advs'] === '0',
- ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'),
+ ivbsCampIdsLocal: readFromLocalStorage('IvbsCampIdsLocal'),
bidParamsJson: JSON.stringify(bidParamsJson),
capCounts: getCappedCampaignsAsString(),
@@ -129,9 +127,21 @@ function buildRequest(bidRequests, bidderRequest) {
purposes: invibes.purposes.toString(),
li: invibes.legitimateInterests.toString(),
- tc: invibes.gdpr_consent
+ tc: invibes.gdpr_consent,
+ isLocalStorageEnabled: storage.hasLocalStorage(),
};
+ let lid = readFromLocalStorage('ivbsdid');
+ if (!lid) {
+ let str = invibes.getCookie('ivbsdid');
+ if (str) {
+ try {
+ let cookieLid = JSON.parse(str);
+ lid = cookieLid.id ? cookieLid.id : cookieLid;
+ } catch (e) {
+ }
+ }
+ }
if (lid) {
data.lId = lid;
}
@@ -172,6 +182,15 @@ function handleResponse(responseObj, bidRequests) {
responseObj = responseObj.body || responseObj;
responseObj = responseObj.videoAdContentResult || responseObj;
+ if (responseObj.ShouldSetLId && responseObj.LId) {
+ if ((!invibes.optIn || !invibes.purposes[0]) && responseObj.PrivacyPolicyRule && responseObj.TcModel && responseObj.TcModel.PurposeConsents) {
+ invibes.optIn = responseObj.PrivacyPolicyRule;
+ invibes.purposes = responseObj.TcModel.PurposeConsents;
+ }
+
+ setInLocalStorage('ivbsdid', responseObj.LId);
+ }
+
if (typeof invibes.bidResponse === 'object') {
if (responseObj.MultipositionEnabled === true) {
invibes.bidResponse.AdPlacements = invibes.bidResponse.AdPlacements.concat(responseObj.AdPlacements);
@@ -411,6 +430,22 @@ function renderCreative(bidModel) {
.replace('creativeHtml', bidModel.CreativeHtml);
}
+function readFromLocalStorage(key) {
+ if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) {
+ return;
+ }
+
+ return storage.getDataFromLocalStorage(key) || '';
+}
+
+function setInLocalStorage(key, value) {
+ if (!invibes.optIn || !invibes.purposes[0]) {
+ return;
+ }
+
+ storage.setDataInLocalStorage(key, value);
+}
+
function getCappedCampaignsAsString() {
const key = 'ivvcap';
@@ -471,14 +506,20 @@ function buildSyncUrl() {
syncUrl += '?visitId=' + invibes.visitId;
syncUrl += '&optIn=' + invibes.optIn;
- const did = invibes.getCookie('ivbsdid');
- if (did) {
- syncUrl += '&ivbsdid=' + encodeURIComponent(did);
+ let did = readFromLocalStorage('ivbsdid');
+ if (!did) {
+ let str = invibes.getCookie('ivbsdid');
+ if (str) {
+ try {
+ let cookieLid = JSON.parse(str);
+ did = cookieLid.id ? cookieLid.id : cookieLid;
+ } catch (e) {
+ }
+ }
}
- const bks = invibes.getCookie('ivvbks');
- if (bks) {
- syncUrl += '&ivvbks=' + encodeURIComponent(bks);
+ if (did) {
+ syncUrl += '&ivbsdid=' + encodeURIComponent(did);
}
return syncUrl;
@@ -486,6 +527,7 @@ function buildSyncUrl() {
function readGdprConsent(gdprConsent) {
if (gdprConsent && gdprConsent.vendorData) {
+ invibes.GdprModuleInstalled = true;
invibes.gdpr_consent = getVendorConsentData(gdprConsent.vendorData);
if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) {
@@ -528,6 +570,7 @@ function readGdprConsent(gdprConsent) {
return 2;
}
+ invibes.GdprModuleInstalled = false;
return 0;
}
@@ -637,34 +680,13 @@ invibes.getCookie = function (name) {
return;
}
- if (!invibes.optIn || !invibes.purposes[0]) {
+ if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) {
return;
}
return storage.getCookie(name);
};
-let initDomainId = function (options) {
- let cookiePersistence = {
- cname: 'ivbsdid',
- load: function () {
- let str = invibes.getCookie(this.cname) || '';
- try {
- return JSON.parse(str);
- } catch (e) {
- }
- }
- };
-
- options = options || {};
-
- var persistence = options.persistence || cookiePersistence;
-
- let state = persistence.load();
-
- return state ? (state.id || state.tempId) : undefined;
-};
-
let keywords = (function () {
const cap = 300;
let headTag = document.getElementsByTagName('head')[0];
@@ -738,7 +760,6 @@ let keywords = (function () {
export function resetInvibes() {
invibes.optIn = undefined;
- invibes.noCookies = undefined;
invibes.dom = undefined;
invibes.bidResponse = undefined;
invibes.domainOptions = undefined;
diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js
index 5d15ae55bfc..1f0bbfd46c3 100644
--- a/modules/invisiblyAnalyticsAdapter.js
+++ b/modules/invisiblyAnalyticsAdapter.js
@@ -6,11 +6,11 @@ import adapter from '../src/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import { generateUUID, logInfo } from '../src/utils.js';
+import CONSTANTS from '../src/constants.json';
const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events';
const analyticsType = 'endpoint';
const analyticsName = 'Invisibly Analytics Adapter:';
-const CONSTANTS = require('../src/constants.json');
const ajax = ajaxBuilder(0);
// Events needed
diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js
index 9c58267298b..6c0a2e5f56d 100644
--- a/modules/iqzoneBidAdapter.js
+++ b/modules/iqzoneBidAdapter.js
@@ -5,6 +5,7 @@ import { config } from '../src/config.js';
const BIDDER_CODE = 'iqzone';
const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs';
+const SYNC_URL = 'https://cs.smartssp.iqzone.com';
function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId ||
@@ -177,6 +178,29 @@ export const spec = {
}
}
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
+ }];
}
};
diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js
index 0d6b0ca189e..1a9321b6852 100644
--- a/modules/ixBidAdapter.js
+++ b/modules/ixBidAdapter.js
@@ -1,14 +1,31 @@
-import { deepAccess, parseGPTSingleSizeArray, inIframe, deepClone, logError, logWarn, isFn, contains, isInteger, isArray, deepSetValue, parseQueryStringParameters, isEmpty, mergeDeep, convertTypes, hasDeviceAccess } from '../src/utils.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { config } from '../src/config.js';
+import {
+ contains,
+ convertTypes,
+ deepAccess,
+ deepClone,
+ deepSetValue,
+ getGptSlotInfoForAdUnitCode,
+ hasDeviceAccess,
+ inIframe,
+ isArray,
+ isEmpty,
+ isFn,
+ isInteger,
+ logError,
+ logWarn,
+ mergeDeep,
+ parseGPTSingleSizeArray,
+ parseQueryStringParameters
+} from '../src/utils.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
import CONSTANTS from '../src/constants.json';
-import { getStorageManager, validateStorageEnforcement } from '../src/storageManager.js';
-import events from '../src/events.js';
-import find from 'core-js-pure/features/array/find.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { INSTREAM, OUTSTREAM } from '../src/video.js';
-import includes from 'core-js-pure/features/array/includes.js';
-import { Renderer } from '../src/Renderer.js';
+import {getStorageManager, validateStorageEnforcement} from '../src/storageManager.js';
+import * as events from '../src/events.js';
+import {find, includes} from '../src/polyfill.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {INSTREAM, OUTSTREAM} from '../src/video.js';
+import {Renderer} from '../src/Renderer.js';
const BIDDER_CODE = 'ix';
const ALIAS_BIDDER_CODE = 'roundel';
@@ -23,11 +40,12 @@ const VIDEO_TIME_TO_LIVE = 3600; // 1hr
const NET_REVENUE = true;
const MAX_REQUEST_SIZE = 8000;
const MAX_REQUEST_LIMIT = 4;
+const OUTSTREAM_MINIMUM_PLAYER_SIZE = [300, 250];
const PRICE_TO_DOLLAR_FACTOR = {
JPY: 1
};
const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html';
-const RENDERER_URL = 'https://js-sec.indexww.com/htv/video-player.js';
+
const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' };
export const ERROR_CODES = {
BID_SIZE_INVALID_FORMAT: 1,
@@ -84,7 +102,7 @@ const VIDEO_PARAMS_ALLOW_LIST = [
];
const LOCAL_STORAGE_KEY = 'ixdiag';
let hasRegisteredHandler = false;
-export const storage = getStorageManager(GLOBAL_VENDOR_ID, BIDDER_CODE);
+export const storage = getStorageManager({gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE});
// Possible values for bidResponse.seatBid[].bid[].mtype which indicates the type of the creative markup so that it can properly be associated with the right sub-object of the BidRequest.Imp.
const MEDIA_TYPES = {
@@ -818,7 +836,8 @@ function buildIXDiag(validBidRequests) {
allu: 0,
ren: false,
version: '$prebid.version$',
- userIds: _getUserIds(validBidRequests[0])
+ userIds: _getUserIds(validBidRequests[0]),
+ url: window.location.href.split('?')[0]
};
// create ad unit map and collect the required diag properties
@@ -840,12 +859,10 @@ function buildIXDiag(validBidRequests) {
if (deepAccess(bid, 'mediaTypes.video.context') === 'outstream') {
ixdiag.ou++;
- // renderer only needed for outstream
-
- const hasRenderer = typeof (deepAccess(bid, 'renderer') || deepAccess(bid, 'mediaTypes.video.renderer')) === 'object';
- // if any one ad unit is missing renderer, set ren status to false in diag
- ixdiag.ren = ixdiag.ren && hasRenderer ? (deepAccess(ixdiag, 'ren')) : hasRenderer;
+ if (isIndexRendererPreferred(bid)) {
+ ixdiag.ren = true;
+ }
}
if (deepAccess(bid, 'mediaTypes.video.context') === 'instream') {
@@ -950,7 +967,7 @@ function getPageUrl() {
* @returns {string}
*/
function detectParamsType(validBidRequest) {
- if (deepAccess(validBidRequest, 'params.video') && deepAccess(validBidRequest, 'mediaTypes.video')) {
+ if (deepAccess(validBidRequest, 'mediaTypes.video') && bidToVideoImp(validBidRequest).video) {
return VIDEO;
}
@@ -1112,24 +1129,18 @@ function getCachedErrors() {
/**
*
- * Initialize Outstream Renderer
+ * Initialize IX Outstream Renderer
* @param {Object} bid
*/
function outstreamRenderer(bid) {
- bid.renderer.push(() => {
- var config = {
- width: bid.width,
- height: bid.height,
- timeout: 3000
- };
-
- // IXOutstreamPlayer supports both vastUrl and vastXml, so we can pass either.
- // Since vastUrl is going to be deprecated from exchange response, vastXml takes priority.
- if (bid.vastXml) {
- window.IXOutstreamPlayer(bid.vastXml, bid.adUnitCode, config);
- } else {
- window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config);
+ bid.renderer.push(function () {
+ const adUnitCode = bid.adUnitCode;
+ const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId;
+ if (!divId) {
+ logWarn(`IX Bid Adapter: adUnitCode: ${divId} not found on page.`);
+ return;
}
+ window.createIXPlayer(divId, bid);
});
}
@@ -1138,10 +1149,10 @@ function outstreamRenderer(bid) {
* @param {string} id
* @returns {Renderer}
*/
-function createRenderer(id) {
+function createRenderer(id, renderUrl) {
const renderer = Renderer.install({
id: id,
- url: RENDERER_URL,
+ url: renderUrl,
loaded: false
});
@@ -1149,11 +1160,37 @@ function createRenderer(id) {
renderer.setRender(outstreamRenderer);
} catch (err) {
logWarn('Prebid Error calling setRender on renderer', err);
+ return null;
+ }
+
+ if (!renderUrl) {
+ logWarn('Outstream renderer URL not found');
+ return null;
}
return renderer;
}
+/**
+ * Returns whether our renderer could potentially be used.
+ * @param {*} bid bid object
+ */
+function isIndexRendererPreferred(bid) {
+ if (deepAccess(bid, 'mediaTypes.video.context') !== 'outstream') {
+ return false;
+ }
+
+ // ad unit renderer could be on the adUnit.mediaTypes.video level or adUnit level
+ let renderer = deepAccess(bid, 'mediaTypes.video.renderer');
+ if (!renderer) {
+ renderer = deepAccess(bid, 'renderer');
+ }
+
+ const isValid = !!(typeof (renderer) === 'object' && renderer.url && renderer.render);
+ // if renderer on the adunit is not valid or it's only a backup, our renderer may be used
+ return !isValid || renderer.backupOnly;
+}
+
export const spec = {
code: BIDDER_CODE,
@@ -1237,6 +1274,17 @@ export const spec = {
return false;
}
}
+
+ const videoImp = bidToVideoImp(bid).video;
+ if (deepAccess(bid, 'mediaTypes.video.context') === OUTSTREAM && isIndexRendererPreferred(bid) && videoImp) {
+ const outstreamPlayerSize = deepAccess(videoImp, 'playerSize')[0];
+ const isValidSize = outstreamPlayerSize[0] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[0] && outstreamPlayerSize[1] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[1];
+ if (!isValidSize) {
+ logError(`IX Bid Adapter: ${mediaTypeVideoPlayerSize} is an invalid size for IX outstream renderer`);
+ return false;
+ }
+ }
+
return true;
},
@@ -1347,8 +1395,12 @@ export const spec = {
const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp, bidderRequest.validBidRequests);
bid = parseBid(innerBids[j], responseBody.cur, bidRequest);
- if (!deepAccess(bid, 'mediaTypes.video.renderer') && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') {
- bid.renderer = createRenderer(innerBids[j].bidId);
+ if (bid.mediaType === VIDEO && isIndexRendererPreferred(bidRequest)) {
+ const renderUrl = deepAccess(responseBody, 'ext.videoplayerurl');
+ bid.renderer = createRenderer(innerBids[j].bidId, renderUrl);
+ if (!bid.renderer) {
+ continue;
+ }
}
bids.push(bid);
diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md
index 59b699bad2d..415fdc9db65 100644
--- a/modules/ixBidAdapter.md
+++ b/modules/ixBidAdapter.md
@@ -70,10 +70,10 @@ object are detailed here.
| siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size is provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'`
| size | Optional (Deprecated)| Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]`
| video | Optional | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level.
-|video.w| Required | Integer | The video player size width in pixels that will be passed to demand partners.
-|video.h| Required | Integer | The video player size height in pixels that will be passed to demand partners.
-|video.playerSize| Optional* | Integer | The video player size that will be passed to demand partners. * In the absence of `video.w` and `video.h`, this field is required.
-| video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video.
+|video.w| Required | Integer | The width of the video player in pixels that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `bidder` level, this is a required field. You must define the size of the video player using the `video.w` and `video.h` parameters, with a minimum video player size of 300 x 250.
+|video.h| Required | Integer | The height of the video player in pixels that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `bidder` level, this is a required field. You must define the size of the video player using the `video.w` and `video.h` parameters, with a minimum video player size of 300 x 250.
+|video.playerSize| Optional* | Array[Integer,Integer] | The video player size that will be passed to demand partners. *If you are using Index’s outstream player and have placed the `video` object at the `adUnit` level, this is a required field. You must define the size of the video player using this parameter, with a minimum video player size of 300 x 250.
+| video.mimes | Required | String[] | If you are using Index’s outstream video player and want to learn more about what is supported, see [List of supported OpenRTB bid request fields for Sellers](https://kb.indexexchange.com/publishers/openrtb_integration/list_of_supported_openrtb_bid_request_fields_for_sellers.htm#Video).
|video.minduration| Required | Integer | Minimum video ad duration in seconds.
|video.maxduration| Required | Integer | Maximum video ad duration in seconds.
|video.protocol / video.protocols| Required | Integer / Integer[] | Either a single protocol provided as an integer, or protocols provided as a list of integers. `2` - VAST 2.0, `3` - VAST 3.0, `5` - VAST 2.0 Wrapper, `6` - VAST 3.0 Wrapper
@@ -111,9 +111,7 @@ Both video and banner params will be read from the `adUnits[].mediaTypes.video`
The examples in this guide assume the following starting configuration (you may remove banner or video, if either does not apply).
-In regards to video, `context` can either be `'instream'` or `'outstream'`. Note that `outstream` requires additional configuration on the adUnit.
-
-
+In regards to video, `context` can either be `'instream'` or `'outstream'`.
```javascript
var adUnits = [{
@@ -195,9 +193,9 @@ var adUnits = [{
context: 'instream',
playerSize: [300, 250],
mimes: [
- 'video/mp4',
- 'video/webm'
- ],
+ 'video/mp4',
+ 'video/webm'
+ ],
minduration: 0,
maxduration: 60,
protocols: [6]
@@ -224,45 +222,47 @@ Please note that you can re-use the existing `siteId` within the same flex
position.
**Video (Outstream):**
-Note that currently, outstream video rendering must be configured by the publisher. In the adUnit, a `renderer` object must be defined, which includes a `url` pointing to the video rendering script, and a `render` function for creating the video player. See http://prebid.org/dev-docs/show-outstream-video-ads.html for more information.
+
+Publishers have two options to receive outstream video demand from Index:
+* Using Index’s outstream video player
+* In an outstream video configuration set up by the publisher. For more information, see [Prebid’s documentation on how to show video ads.](https://docs.prebid.org/dev-docs/show-outstream-video-ads.html)
+
+**Index’s outstream video player**
+Publishers who are using Index as a bidding adapter in Prebid.js can show outstream video ads on their site from us by using Index’s outstream video player. This allows a video ad to display inside of a video player and can be placed anywhere on a publisher’s site, such as in-article, in-feed, and more.
+
+Define a new `video` object for our outstream video player at either the adUnit level or the `bidder` level. If you are setting it at the bidder level, define the size of the video player using the parameters `video.h` and `video.w`. If you are setting it at the `adUnit` level, define the size using video.playerSize.
+
+For more information on how to structure the `video` object, refer to the following code example:
+
```javascript
var adUnits = [{
- code: 'video-div-a',
+ code: 'div-gpt-ad-1571167646410-1',
mediaTypes: {
video: {
+ playerSize: [640, 360],
context: 'outstream',
- playerSize: [300, 250],
- mimes: [
- 'video/mp4',
- 'video/webm'
- ],
- minduration: 0,
- maxduration: 60,
- protocols: [6]
- }
- },
- renderer: {
- url: 'https://test.com/my-video-player.js',
- render: function (bid) {
- ...
+ api: [2],
+ protocols: [2, 3, 5, 6],
+ minduration: 5,
+ maxduration: 30,
+ mimes: ['video/mp4', 'application/javascript'],
+ placement: 3
}
},
bids: [{
bidder: 'ix',
params: {
- siteId: '12345',
- video: {
- // If required, use this to override mediaTypes.video.XX properties
- }
+ siteId: '715964'
}
}]
}];
```
+Please note that your use of the outstream video player will be governed by and subject to the terms and conditions of i) any master services or license agreement entered into by you and Index Exchange; ii) the information provided on our knowledge base linked [here](https://kb.indexexchange.com/publishers/prebid_integration/outstream_video_prebidjs.htm) and [here](https://kb.indexexchange.com/publishers/guidelines/standard_contractual_clauses.htm), and iii) our [Privacy Policy](https://www.indexexchange.com/privacy/). Your use of Index’s outstream video player constitutes your acknowledgement and acceptance of the foregoing.
#### Video Caching
-Note that the IX adapter expects a client-side Prebid Cache to be enabled for video bidding.
+Note that the IX adapter expects a client-side Prebid Cache to be enabled for instream video bidding.
```
pbjs.setConfig({
@@ -293,21 +293,21 @@ pbjs.setConfig({
By default, the IX bidding adapter bids on all banner sizes available in the ad unit when configured to at least one banner size. If you want the IX bidding adapter to only bid on the banner size it’s configured to, switch off this feature using `detectMissingSizes`.
```
pbjs.setConfig({
- ix: {
- detectMissingSizes: false
- }
- });
+ ix: {
+ detectMissingSizes: false
+ }
+});
```
OR
```
pbjs.setBidderConfig({
- bidders: ["ix"],
- config: {
- ix: {
- detectMissingSizes: false
- }
- }
- });
+ bidders: ["ix"],
+ config: {
+ ix: {
+ detectMissingSizes: false
+ }
+ }
+});
```
### 2. Include `ixBidAdapter` in your build process
diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js
index 119fcdf142b..90ea17395f7 100644
--- a/modules/jixieBidAdapter.js
+++ b/modules/jixieBidAdapter.js
@@ -1,4 +1,4 @@
-import { logWarn, parseUrl, deepAccess, isArray } from '../src/utils.js';
+import { logWarn, parseUrl, deepAccess, isArray, getDNT } from '../src/utils.js';
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
@@ -6,9 +6,10 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { ajax } from '../src/ajax.js';
import { getRefererInfo } from '../src/refererDetection.js';
import { Renderer } from '../src/Renderer.js';
-export const storage = getStorageManager();
+import {createEidsArray} from './userId/eids.js';
const BIDDER_CODE = 'jixie';
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
const EVENTS_URL = 'https://hbtra.jixie.io/sync/hb?';
const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.media/jxhbrenderer.1.1.min.js';
const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost';
@@ -59,9 +60,16 @@ function fetchIds_() {
return ret;
}
+// device in the payload had been a simple string ('desktop', 'mobile')
+// Now changed to an object. yes the backend is able to handle it.
function getDevice_() {
- return ((/(ios|ipod|ipad|iphone|android|blackberry|iemobile|opera mini|webos)/i).test(navigator.userAgent)
- ? 'mobile' : 'desktop');
+ const device = config.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] : '';
+ return device;
}
function pingTracking_(endpointOverride, qpobj) {
@@ -121,6 +129,17 @@ function getMiscDims_() {
return ret;
}
+/* function addUserId(eids, id, source, rti) {
+ if (id) {
+ if (rti) {
+ eids.push({ source, id, rti_partner: rti });
+ } else {
+ eids.push({ source, id });
+ }
+ }
+ return eids;
+} */
+
// easier for replacement in the unit test
export const internal = {
getDevice: getDevice_,
@@ -163,7 +182,22 @@ export const spec = {
}
let ids = fetchIds_();
+ let eids = [];
let miscDims = internal.getMiscDims();
+
+ // all available user ids are sent to our backend in the standard array layout:
+ if (validBidRequests[0].userId) {
+ let eids1 = createEidsArray(validBidRequests[0].userId);
+ if (eids1.length) {
+ eids = eids1;
+ }
+ }
+ // we want to send this blob of info to our backend:
+ let pg = config.getConfig('priceGranularity');
+ if (!pg) {
+ pg = {};
+ }
+
let transformedParams = Object.assign({}, {
auctionid: bidderRequest.auctionId,
timeout: bidderRequest.timeout,
@@ -174,6 +208,8 @@ export const spec = {
pageurl: miscDims.pageurl,
mkeywords: miscDims.mkeywords,
bids: bids,
+ eids: eids,
+ pricegranularity: pg,
cfg: jixieCfgBlob
}, ids);
return Object.assign({}, {
diff --git a/modules/jixieBidAdapter.md b/modules/jixieBidAdapter.md
index d9c1f19541d..c0a1a965e87 100644
--- a/modules/jixieBidAdapter.md
+++ b/modules/jixieBidAdapter.md
@@ -7,6 +7,7 @@ Maintainer: contact@jixie.io
# Description
Module that connects to Jixie demand source to fetch bids.
+All prebid-supported user ids are sent to Jixie endpoint, if available.
# Test Parameters
```
diff --git a/modules/justIdSystem.js b/modules/justIdSystem.js
index d30b9f3073f..15b1c90da4e 100644
--- a/modules/justIdSystem.js
+++ b/modules/justIdSystem.js
@@ -8,7 +8,7 @@
import * as utils from '../src/utils.js'
import { submodule } from '../src/hook.js'
import { loadExternalScript } from '../src/adloader.js'
-import includes from 'core-js-pure/features/array/includes.js';
+import {includes} from '../src/polyfill.js';
const MODULE_NAME = 'justId';
const EXTERNAL_SCRIPT_MODULE_CODE = 'justtag';
diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js
index 56f9935ea6e..e2ba92d51d9 100644
--- a/modules/justpremiumBidAdapter.js
+++ b/modules/justpremiumBidAdapter.js
@@ -4,8 +4,7 @@ import { deepAccess } from '../src/utils.js';
const BIDDER_CODE = 'justpremium'
const GVLID = 62
const ENDPOINT_URL = 'https://pre.ads.justpremium.com/v/2.0/t/xhr'
-const JP_ADAPTER_VERSION = '1.8.1'
-const pixels = []
+const JP_ADAPTER_VERSION = '1.8.3'
export const spec = {
code: BIDDER_CODE,
@@ -19,6 +18,7 @@ export const spec = {
buildRequests: (validBidRequests, bidderRequest) => {
const c = preparePubCond(validBidRequests)
const dim = getWebsiteDim()
+ const ggExt = getGumGumParams()
const payload = {
zone: validBidRequests.map(b => {
return parseInt(b.params.zone)
@@ -32,7 +32,8 @@ export const spec = {
wh: dim.innerHeight,
c: c,
id: validBidRequests[0].params.zone,
- sizes: {}
+ sizes: {},
+ ggExt: ggExt
}
validBidRequests.forEach(b => {
const zone = b.params.zone
@@ -112,8 +113,10 @@ export const spec = {
return bidResponses
},
- getUserSyncs: function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) {
+ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
let url = 'https://pre.ads.justpremium.com/v/1.0/t/sync' + '?_c=' + 'a' + Math.random().toString(36).substring(7) + Date.now();
+ let pixels = []
+
if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean') && gdprConsent.gdprApplies && gdprConsent.consentString) {
url = url + '&consentString=' + encodeURIComponent(gdprConsent.consentString)
}
@@ -126,6 +129,10 @@ export const spec = {
url: url
})
}
+ if (syncOptions.pixelEnabled && serverResponses.length !== 0) {
+ const pxsFromResponse = serverResponses.map(res => res?.body?.pxs).reduce((acc, cur) => acc.concat(cur), []).filter((obj) => obj !== undefined);
+ pixels = [...pixels, ...pxsFromResponse];
+ }
return pixels
},
}
@@ -253,4 +260,19 @@ function getWebsiteDim () {
}
}
+function getGumGumParams () {
+ if (!window.top) return null
+
+ const urlParams = new URLSearchParams(window.top.location.search)
+ const ggParams = {
+ 'ggAdbuyid': urlParams.get('gg_adbuyid'),
+ 'ggDealid': urlParams.get('gg_dealid'),
+ 'ggEadbuyid': urlParams.get('gg_eadbuyid')
+ }
+
+ const checkIfEmpty = (obj) => Object.keys(obj).length === 0 ? null : obj
+ const removeNullEntries = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null))
+ return checkIfEmpty(removeNullEntries(ggParams))
+}
+
registerBidder(spec)
diff --git a/modules/justpremiumBidAdapter.md b/modules/justpremiumBidAdapter.md
index 45dcb7b7f99..e107cb80958 100644
--- a/modules/justpremiumBidAdapter.md
+++ b/modules/justpremiumBidAdapter.md
@@ -2,7 +2,6 @@
**Module Name**: Justpremium Bidder Adapter
**Module Type**: Bidder Adapter
-**Maintainer**: headerbidding-dev@justpremium.com
# Description
diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js
index 5a7e9f13686..6cccd660854 100644
--- a/modules/jwplayerRtdProvider.js
+++ b/modules/jwplayerRtdProvider.js
@@ -9,14 +9,15 @@
* @requires module:modules/realTimeData
*/
-import { submodule } from '../src/hook.js';
-import { config } from '../src/config.js';
-import { ajaxBuilder } from '../src/ajax.js';
-import { logError } from '../src/utils.js';
-import find from 'core-js-pure/features/array/find.js';
-import { getGlobal } from '../src/prebidGlobal.js';
+import {submodule} from '../src/hook.js';
+import {config} from '../src/config.js';
+import {ajaxBuilder} from '../src/ajax.js';
+import {logError} from '../src/utils.js';
+import {find} from '../src/polyfill.js';
+import {getGlobal} from '../src/prebidGlobal.js';
const SUBMODULE_NAME = 'jwplayer';
+const JWPLAYER_DOMAIN = SUBMODULE_NAME + '.com';
const segCache = {};
const pendingRequests = {};
let activeRequestCount = 0;
@@ -69,7 +70,7 @@ export function fetchTargetingForMediaId(mediaId) {
const ajax = ajaxBuilder();
// TODO: Avoid checking undefined vs null by setting a callback to pendingRequests.
pendingRequests[mediaId] = null;
- ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, {
+ ajax(`https://cdn.${JWPLAYER_DOMAIN}/v2/media/${mediaId}`, {
success: function (response) {
const segment = parseSegment(response);
cacheSegments(segment, mediaId);
@@ -155,10 +156,17 @@ export function enrichAdUnits(adUnits) {
if (!vat) {
return;
}
- const contentId = getContentId(vat.mediaID);
- const contentData = getContentData(vat.segments);
+ const mediaId = vat.mediaID;
+ const contentId = getContentId(mediaId);
+ const contentSegments = getContentSegments(vat.segments);
+ const contentData = getContentData(mediaId, contentSegments);
const targeting = formatTargetingResponse(vat);
enrichBids(adUnit.bids, targeting, contentId, contentData);
+ let ortb2 = config.getConfig('ortb2');
+ ortb2 = getOrtbSiteContent(ortb2, contentId, contentData);
+ if (ortb2) {
+ config.setConfig({ ortb2 });
+ }
};
loadVat(jwTargeting, onVatResponse);
});
@@ -263,7 +271,7 @@ export function getContentId(mediaID) {
return 'jw_' + mediaID;
}
-export function getContentData(segments) {
+export function getContentSegments(segments) {
if (!segments || !segments.length) {
return;
}
@@ -276,21 +284,40 @@ export function getContentData(segments) {
return convertedSegments;
}, []);
- return {
- name: 'jwplayer',
- ext: {
- segtax: 502
- },
- segment: formattedSegments
+ return formattedSegments;
+}
+
+export function getContentData(mediaId, segments) {
+ if (!mediaId && !segments) {
+ return;
+ }
+
+ const contentData = {
+ name: JWPLAYER_DOMAIN,
+ ext: {}
};
+
+ if (mediaId) {
+ contentData.ext.cids = [mediaId];
+ }
+
+ if (segments) {
+ contentData.segment = segments;
+ contentData.ext.segtax = 502;
+ }
+
+ return contentData;
}
-export function addOrtbSiteContent(bid, contentId, contentData) {
+export function getOrtbSiteContent(ortb2, contentId, contentData) {
if (!contentId && !contentData) {
return;
}
- let ortb2 = bid.ortb2 || {};
+ if (!ortb2) {
+ ortb2 = {};
+ }
+
let site = ortb2.site = ortb2.site || {};
let content = site.content = site.content || {};
@@ -298,12 +325,17 @@ export function addOrtbSiteContent(bid, contentId, contentData) {
content.id = contentId;
}
+ const currentData = content.data = content.data || [];
+ // remove old jwplayer data
+ const data = currentData.filter(datum => datum.name !== JWPLAYER_DOMAIN);
+
if (contentData) {
- const data = content.data = content.data || [];
data.push(contentData);
}
- bid.ortb2 = ortb2;
+ content.data = data;
+
+ return ortb2;
}
function enrichBids(bids, targeting, contentId, contentData) {
@@ -313,7 +345,10 @@ function enrichBids(bids, targeting, contentId, contentData) {
bids.forEach(bid => {
addTargetingToBid(bid, targeting);
- addOrtbSiteContent(bid, contentId, contentData);
+ const ortb2 = getOrtbSiteContent(bid.ortb2, contentId, contentData);
+ if (ortb2) {
+ bid.ortb2 = ortb2;
+ }
});
}
@@ -334,7 +369,7 @@ export function addTargetingToBid(bid, targeting) {
function getPlayer(playerID) {
const jwplayer = window.jwplayer;
if (!jwplayer) {
- logError('jwplayer.js was not found on page');
+ logError(SUBMODULE_NAME + '.js was not found on page');
return;
}
diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md
index 77f65909040..479829196ed 100644
--- a/modules/jwplayerRtdProvider.md
+++ b/modules/jwplayerRtdProvider.md
@@ -95,9 +95,10 @@ Example:
content: {
id: 'jw_abc123',
data: [{
- name: 'jwplayer',
+ name: 'jwplayer.com',
ext: {
- segtax: 502
+ segtax: 502,
+ cids: ['abc123']
},
segment: [{
id: '123'
@@ -117,8 +118,9 @@ where:
- `content` is an object containing metadata for the media. It may contain the following information:
- `id` is a unique identifier for the specific media asset
- `data` is an array containing segment taxonomy objects that have the following parameters:
- - `name` is the `jwplayer` string indicating the provider name
+ - `name` is the `jwplayer.com` string indicating the provider name
- `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy
+ - `ext.cids` is an array containing the list of extended content ids as defined in [oRTB's community extensions](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/extended-content-ids.md#example---content-id-and-seller-defined-context).
- `segment` is an array containing the segment taxonomy values as an object where:
- `id` is the string representation of the data segment value.
diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js
index ff56c97e7b7..1842231721b 100644
--- a/modules/kargoBidAdapter.js
+++ b/modules/kargoBidAdapter.js
@@ -10,7 +10,7 @@ const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={I
const SYNC_COUNT = 5;
const GVLID = 972;
const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]
-const storage = getStorageManager(GVLID, BIDDER_CODE);
+const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
let sessionId,
lastPageUrl,
@@ -70,27 +70,42 @@ export const spec = {
const bidResponses = [];
for (let bidId in bids) {
let adUnit = bids[bidId];
- let meta;
+ let meta = {
+ mediaType: BANNER
+ };
+
if (adUnit.metadata && adUnit.metadata.landingPageDomain) {
- meta = {
- clickUrl: adUnit.metadata.landingPageDomain[0],
- advertiserDomains: adUnit.metadata.landingPageDomain
- };
+ meta.clickUrl = adUnit.metadata.landingPageDomain[0];
+ meta.advertiserDomains = adUnit.metadata.landingPageDomain;
+ }
+
+ if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) {
+ meta.mediaType = adUnit.mediaType;
}
- bidResponses.push({
+
+ const bidResponse = {
requestId: bidId,
cpm: Number(adUnit.cpm),
width: adUnit.width,
height: adUnit.height,
- ad: adUnit.adm,
ttl: 300,
creativeId: adUnit.id,
dealId: adUnit.targetingCustom,
netRevenue: true,
- currency: bidRequest.currency,
+ currency: adUnit.currency || bidRequest.currency,
+ mediaType: meta.mediaType,
meta: meta
- });
+ };
+
+ if (meta.mediaType == VIDEO) {
+ bidResponse.vastXml = adUnit.adm;
+ } else {
+ bidResponse.ad = adUnit.adm;
+ }
+
+ bidResponses.push(bidResponse);
}
+
return bidResponses;
},
getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) {
@@ -172,28 +187,6 @@ export const spec = {
return spec._getCrbFromCookie();
},
- _getKruxUserId() {
- return spec._getLocalStorageSafely('kxkar_user');
- },
-
- _getKruxSegments() {
- return spec._getLocalStorageSafely('kxkar_segs');
- },
-
- _getKrux() {
- const segmentsStr = spec._getKruxSegments();
- let segments = [];
-
- if (segmentsStr) {
- segments = segmentsStr.split(',');
- }
-
- return {
- userID: spec._getKruxUserId(),
- segments: segments
- };
- },
-
_getLocalStorageSafely(key) {
try {
return storage.getDataFromLocalStorage(key);
@@ -205,7 +198,7 @@ export const spec = {
_getUserIds(tdid, usp, gdpr) {
const crb = spec._getCrb();
const userIds = {
- kargoID: crb.userId,
+ kargoID: crb.lexId,
clientID: crb.clientId,
crbIDs: crb.syncIds || {},
optOut: crb.optOut,
@@ -235,7 +228,6 @@ export const spec = {
_getAllMetadata(tdid, usp, gdpr) {
return {
userIDs: spec._getUserIds(tdid, usp, gdpr),
- krux: spec._getKrux(),
pageURL: window.location.href,
rawCRB: spec._readCookie('krg_crb'),
rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb')
diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js
index 49be80e969c..80aa038a9f7 100644
--- a/modules/koblerBidAdapter.js
+++ b/modules/koblerBidAdapter.js
@@ -63,7 +63,7 @@ export const onBidWon = function (bid) {
const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0);
const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY;
if (isStr(bid.nurl) && bid.nurl !== '') {
- const winNotificationUrl = replaceAuctionPrice(bid.nurl, cpm)
+ const winNotificationUrl = replaceAuctionPrice(bid.nurl, bid.originalCpm || cpm)
.replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency)
.replace(/\${AD_SERVER_PRICE}/g, adServerPrice)
.replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency);
diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js
index b04b2124dd8..a278a587038 100644
--- a/modules/limelightDigitalBidAdapter.js
+++ b/modules/limelightDigitalBidAdapter.js
@@ -1,7 +1,7 @@
-import { logMessage, groupBy, uniques, flatten, deepAccess } from '../src/utils.js';
+import { logMessage, groupBy, flatten, uniques } from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import {ajax} from '../src/ajax.js';
+import { ajax } from '../src/ajax.js';
const BIDDER_CODE = 'limelightDigital';
@@ -94,23 +94,20 @@ export const spec = {
},
getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
- const syncs = serverResponses.map(response => response.body).reduce(flatten, [])
- .map(response => deepAccess(response, 'ext.sync')).filter(Boolean);
- const iframeSyncUrls = !syncOptions.iframeEnabled ? [] : syncs.map(sync => sync.iframe).filter(Boolean)
- .filter(uniques).map(url => {
- return {
- type: 'iframe',
- url: url
- }
- });
- const pixelSyncUrls = !syncOptions.pixelEnabled ? [] : syncs.map(sync => sync.pixel).filter(Boolean)
- .filter(uniques).map(url => {
- return {
- type: 'image',
- url: url
- }
- });
- return [iframeSyncUrls, pixelSyncUrls].reduce(flatten, []);
+ const iframeSyncs = [];
+ const imageSyncs = [];
+ for (let i = 0; i < serverResponses.length; i++) {
+ const serverResponseHeaders = serverResponses[i].headers;
+ const imgSync = (serverResponseHeaders != null && syncOptions.pixelEnabled) ? serverResponseHeaders.get('X-PLL-UserSync-Image') : null
+ const iframeSync = (serverResponseHeaders != null && syncOptions.iframeEnabled) ? serverResponseHeaders.get('X-PLL-UserSync-Iframe') : null
+ if (iframeSync != null) {
+ iframeSyncs.push(iframeSync)
+ } else if (imgSync != null) {
+ imageSyncs.push(imgSync)
+ }
+ }
+ return [iframeSyncs.filter(uniques).map(it => { return { type: 'iframe', url: it } }),
+ imageSyncs.filter(uniques).map(it => { return { type: 'image', url: it } })].reduce(flatten, []).filter(uniques);
}
};
diff --git a/modules/limelightDigitalBidAdapter.md b/modules/limelightDigitalBidAdapter.md
index ab69ef8eaa4..a4abb6f1411 100644
--- a/modules/limelightDigitalBidAdapter.md
+++ b/modules/limelightDigitalBidAdapter.md
@@ -22,7 +22,7 @@ var adUnits = [{
bids: [{
bidder: 'limelightDigital',
params: {
- host: 'exchange.ortb.net',
+ host: 'exchange-9qao.ortb.net',
adUnitId: 0,
adUnitType: 'banner'
}
@@ -38,7 +38,7 @@ var videoAdUnit = [{
bids: [{
bidder: 'limelightDigital',
params: {
- host: 'exchange.ortb.net',
+ host: 'exchange-9qao.ortb.net',
adUnitId: 0,
adUnitType: 'video'
}
diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js
index 91415daa497..68cbb3b2412 100644
--- a/modules/liveIntentIdSystem.js
+++ b/modules/liveIntentIdSystem.js
@@ -13,7 +13,7 @@ import { getStorageManager } from '../src/storageManager.js';
import { MinimalLiveConnect } from 'live-connect-js/esm/minimal-live-connect.js';
const MODULE_NAME = 'liveIntentId';
-export const storage = getStorageManager(null, MODULE_NAME);
+export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME});
const calls = {
ajaxGet: (url, onSuccess, onError, timeout) => {
ajaxBuilder(timeout)(
diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js
index 25b919956e0..1116fd99ba0 100644
--- a/modules/livewrappedAnalyticsAdapter.js
+++ b/modules/livewrappedAnalyticsAdapter.js
@@ -86,6 +86,8 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE
bidResponse.readyToSend = 1;
bidResponse.mediaType = args.mediaType == 'native' ? 2 : (args.mediaType == 'video' ? 4 : 1);
bidResponse.floorData = args.floorData;
+ bidResponse.meta = args.meta;
+
if (!bidResponse.ttr) {
bidResponse.ttr = time - bidResponse.start;
}
@@ -115,6 +117,8 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE
wonBid.won = true;
wonBid.floorData = args.floorData;
wonBid.rUp = args.rUp;
+ wonBid.meta = args.meta;
+ wonBid.dealId = args.dealId;
if (wonBid.sendStatus != 0) {
livewrappedAnalyticsAdapter.sendEvents();
}
@@ -251,7 +255,8 @@ function getResponses(gdpr, auctionIds) {
auctionId: auctionIdPos,
auc: bid.auc,
buc: bid.buc,
- lw: bid.lw
+ lw: bid.lw,
+ meta: bid.meta
});
}
});
@@ -290,7 +295,9 @@ function getWins(gdpr, auctionIds) {
auc: bid.auc,
buc: bid.buc,
lw: bid.lw,
- rUp: bid.rUp
+ rUp: bid.rUp,
+ meta: bid.meta,
+ dealId: bid.dealId
});
}
});
diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js
index 6b7c055b295..8afeaf80652 100644
--- a/modules/livewrappedBidAdapter.js
+++ b/modules/livewrappedBidAdapter.js
@@ -1,13 +1,12 @@
-import { isSafariBrowser, deepAccess, getWindowTop, mergeDeep } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { config } from '../src/config.js';
-import find from 'core-js-pure/features/array/find.js';
-import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
-import { getStorageManager } from '../src/storageManager.js';
-
-export const storage = getStorageManager();
+import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {config} from '../src/config.js';
+import {find} from '../src/polyfill.js';
+import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {getStorageManager} from '../src/storageManager.js';
const BIDDER_CODE = 'livewrapped';
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const URL = 'https://lwadm.com/ad';
const VERSION = '1.4';
@@ -68,7 +67,7 @@ export const spec = {
var adRequests = bidRequests.map(bidToAdRequest);
if (eids) {
- ortb2 = mergeDeep(ortb2 || {}, eids);
+ ortb2 = mergeDeep(mergeDeep({}, ortb2 || {}), eids);
}
const payload = {
diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js
index 275ab38915d..e58c643f4f0 100644
--- a/modules/lkqdBidAdapter.js
+++ b/modules/lkqdBidAdapter.js
@@ -36,7 +36,6 @@ export const spec = {
const serverRequestObjects = [];
const UTC_OFFSET = new Date().getTimezoneOffset();
const UA = navigator.userAgent;
- const IP = navigator.ip ? navigator.ip : 'prebid.js';
const USP = BIDDER_REQUEST.uspConsent || null;
const REFERER = BIDDER_REQUEST.refererInfo ? new URL(BIDDER_REQUEST.refererInfo.referer).hostname : window.location.hostname;
const BIDDER_GDPR = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.gdprApplies ? 1 : null;
@@ -60,8 +59,7 @@ export const spec = {
ua: UA,
geo: {
utcoffset: UTC_OFFSET
- },
- ip: IP
+ }
},
user: {
ext: {}
diff --git a/modules/loglyliftBidAdapter.js b/modules/loglyliftBidAdapter.js
index e1319d08766..dd5f0af1cdf 100644
--- a/modules/loglyliftBidAdapter.js
+++ b/modules/loglyliftBidAdapter.js
@@ -1,13 +1,13 @@
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { NATIVE } from '../src/mediaTypes.js';
+import { BANNER, NATIVE } from '../src/mediaTypes.js';
const BIDDER_CODE = 'loglylift';
const ENDPOINT_URL = 'https://bid.logly.co.jp/prebid/client/v1';
export const spec = {
code: BIDDER_CODE,
- supportedMediaTypes: [NATIVE],
+ supportedMediaTypes: [BANNER, NATIVE],
isBidRequestValid: function (bid) {
return !!(bid.params && bid.params.adspotId);
@@ -43,7 +43,8 @@ export const spec = {
getUserSyncs: function (syncOptions, serverResponses) {
const syncs = [];
- if (syncOptions.iframeEnabled && serverResponses.length > 0) {
+ // sync if mediaType is native because not native ad itself has a function for sync
+ if (syncOptions.iframeEnabled && serverResponses.length > 0 && serverResponses[0].body.bids[0].native) {
syncs.push({
type: 'iframe',
url: 'https://sync.logly.co.jp/sync/sync.html'
diff --git a/modules/loglyliftBidAdapter.md b/modules/loglyliftBidAdapter.md
index 9bca238b03e..5505d66957d 100644
--- a/modules/loglyliftBidAdapter.md
+++ b/modules/loglyliftBidAdapter.md
@@ -12,6 +12,22 @@ Currently module supports only native mediaType.
# Test Parameters
```
var adUnits = [
+ // Banner adUnit
+ {
+ code: 'test-banner-code',
+ sizes: [[300, 250], [300, 600]],
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300, 600]]
+ }
+ },
+ bids: [{
+ bidder: 'loglylift',
+ params: {
+ adspotId: 1302078
+ }
+ }]
+ },
// Native adUnit
{
code: 'test-native-code',
diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js
index 82503a57e9e..a03626d4a1f 100644
--- a/modules/lotamePanoramaIdSystem.js
+++ b/modules/lotamePanoramaIdSystem.js
@@ -29,7 +29,7 @@ const DAY_MS = 60 * 60 * 24 * 1000;
const MISSING_CORE_CONSENT = 111;
const GVLID = 95;
-export const storage = getStorageManager(GVLID, MODULE_NAME);
+export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME});
let cookieDomain;
/**
diff --git a/modules/malltvAnalyticsAdapter.js b/modules/malltvAnalyticsAdapter.js
index 3431681ef2f..a0e2a208bc9 100644
--- a/modules/malltvAnalyticsAdapter.js
+++ b/modules/malltvAnalyticsAdapter.js
@@ -184,5 +184,5 @@ malltvAnalyticsAdapter.enableAnalytics = function (config) {
adapterManager.registerAnalyticsAdapter({
adapter: malltvAnalyticsAdapter,
- code: 'malltvAnalytics'
+ code: 'malltv'
})
diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js
index 218bd2f1a5e..53f745d4004 100644
--- a/modules/malltvBidAdapter.js
+++ b/modules/malltvBidAdapter.js
@@ -9,7 +9,7 @@ const SIZE_SEPARATOR = ';';
const BISKO_ID = 'biskoId';
const STORAGE_ID = 'bisko-sid';
const SEGMENTS = 'biskoSegments';
-const storage = getStorageManager();
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js
index 61b7c31c8e4..8d62b0ffba7 100644
--- a/modules/mantisBidAdapter.js
+++ b/modules/mantisBidAdapter.js
@@ -1,7 +1,7 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
-export const storage = getStorageManager();
+export const storage = getStorageManager({bidderCode: 'mantis'});
function inIframe() {
try {
diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js
index 79e2148084a..92374b748c7 100644
--- a/modules/marsmediaBidAdapter.js
+++ b/modules/marsmediaBidAdapter.js
@@ -16,8 +16,7 @@ function MarsmediaAdapter() {
let SUPPORTED_VIDEO_DELIVERY = [1];
let SUPPORTED_VIDEO_API = [1, 2, 5];
let slotsToBids = {};
- let that = this;
- let version = '2.4';
+ let version = '2.5';
this.isBidRequestValid = function (bid) {
return !!(bid.params && bid.params.zoneId);
@@ -288,7 +287,6 @@ function MarsmediaAdapter() {
let bidRequest = slotsToBids[bid.impid];
let bidResponse = {
requestId: bidRequest.bidId,
- bidderCode: that.code,
cpm: parseFloat(bid.price),
width: bid.w,
height: bid.h,
diff --git a/modules/mass.js b/modules/mass.js
index ac22f04e8db..f38f833f4d3 100644
--- a/modules/mass.js
+++ b/modules/mass.js
@@ -2,8 +2,8 @@
* This module adds MASS support to Prebid.js.
*/
-import { config } from '../src/config.js';
-import { getHook } from '../src/hook.js';
+import {config} from '../src/config.js';
+import {getHook} from '../src/hook.js';
import {auctionManager} from '../src/auctionManager.js';
const defaultCfg = {
diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js
index 7d4f22b7916..c686a2e378d 100644
--- a/modules/mediaforceBidAdapter.js
+++ b/modules/mediaforceBidAdapter.js
@@ -262,7 +262,7 @@ export const spec = {
onBidWon: function(bid) {
const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || '';
if (isStr(bid.burl) && bid.burl !== '') {
- bid.burl = replaceAuctionPrice(bid.burl, cpm);
+ bid.burl = replaceAuctionPrice(bid.burl, bid.originalCpm || cpm);
triggerPixel(bid.burl);
}
},
diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js
new file mode 100644
index 00000000000..b77c965802e
--- /dev/null
+++ b/modules/mediafuseBidAdapter.js
@@ -0,0 +1,1145 @@
+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 { Renderer } from '../src/Renderer.js';
+import { config } from '../src/config.js';
+import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js';
+import { auctionManager } from '../src/auctionManager.js';
+import {find, includes} from '../src/polyfill.js';
+import { OUTSTREAM, INSTREAM } from '../src/video.js';
+import { getStorageManager } from '../src/storageManager.js';
+import { bidderSettings } from '../src/bidderSettings.js';
+
+const BIDDER_CODE = 'mediafuse';
+const URL = 'https://ib.adnxs.com/ut/v3/prebid';
+const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid';
+const VIDEO_TARGETING = ['id', 'minduration', 'maxduration',
+ 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset'];
+const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api'];
+const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language'];
+const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately
+const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout'];
+const VIDEO_MAPPING = {
+ playback_method: {
+ 'unknown': 0,
+ 'auto_play_sound_on': 1,
+ 'auto_play_sound_off': 2,
+ 'click_to_play': 3,
+ 'mouse_over': 4,
+ 'auto_play_sound_unknown': 5
+ },
+ context: {
+ 'unknown': 0,
+ 'pre_roll': 1,
+ 'mid_roll': 2,
+ 'post_roll': 3,
+ 'outstream': 4,
+ 'in-banner': 5
+ }
+};
+const NATIVE_MAPPING = {
+ body: 'description',
+ body2: 'desc2',
+ cta: 'ctatext',
+ image: {
+ serverName: 'main_image',
+ requiredParams: { required: true }
+ },
+ icon: {
+ serverName: 'icon',
+ requiredParams: { required: true }
+ },
+ sponsoredBy: 'sponsored_by',
+ privacyLink: 'privacy_link',
+ salePrice: 'saleprice',
+ displayUrl: 'displayurl'
+};
+const SOURCE = 'pbjs';
+const MAX_IMPS_PER_REQUEST = 15;
+const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/mediafuse-mapping/mappings.json';
+const SCRIPT_TAG_START = '
-
+
+
`;
@@ -512,12 +563,15 @@ const spec = {
publisher: publisherId ? { id: publisherId } : undefined,
page,
domain,
- ref
+ ref,
+ content: { language: getContentLanguage() },
},
imp: validBidRequests.map(slot => mapImpression(slot)),
+ cur: [getCurrency()],
tmax,
user: {},
regs: {},
+ device: { language: getBrowserLanguage() },
test: testMode,
};
@@ -539,6 +593,7 @@ const spec = {
const bids = [];
const site = JSON.parse(request.data).site; // get page and referer data from request
site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn)
+ pageView.sn = site.sn; // store site_name (for syncing and notifications)
let seat;
if (response.seatbid !== undefined) {
@@ -547,6 +602,7 @@ const spec = {
'bidid-' prefix indicates oneCode (parameterless) request and response
*/
response.seatbid.forEach(seatbid => {
+ let creativeCache;
seat = seatbid.seat;
seatbid.bid.forEach(serverBid => {
// get data from bid response
@@ -572,11 +628,12 @@ const spec = {
ext also might contain publisherId and custom ad label
*/
- const { siteid, slotid, pubid, adlabel } = ext;
+ const { siteid, slotid, pubid, adlabel, cache } = ext;
site.id = siteid || site.id;
site.slot = slotid || site.slot;
site.publisherId = pubid;
site.adLabel = adlabel;
+ creativeCache = cache;
}
if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) {
@@ -597,6 +654,7 @@ const spec = {
meta: {
advertiserDomains: adomain,
networkName: seat,
+ pricepl: ext && ext.pricepl,
},
netRevenue: true,
};
@@ -608,6 +666,7 @@ const spec = {
bid.mediaType = 'video';
bid.vastXml = serverBid.adm;
bid.vastContent = serverBid.adm;
+ bid.vastUrl = creativeCache;
} else if (isNativeAd(serverBid)) {
// native
bid.mediaType = 'native';
@@ -662,7 +721,7 @@ const spec = {
if (syncOptions.iframeEnabled && consentApiVersion != 1) {
mySyncs.push({
type: 'iframe',
- url: `${SYNC_URL}?tcf=${consentApiVersion}`,
+ url: `${SYNC_URL}?tcf=${consentApiVersion}&pvid=${pageView.id}&sn=${pageView.sn}`,
});
};
return mySyncs;
diff --git a/modules/sspBCBidAdapter.md b/modules/sspBCBidAdapter.md
index 0da84857cbf..4ae2e425865 100644
--- a/modules/sspBCBidAdapter.md
+++ b/modules/sspBCBidAdapter.md
@@ -21,6 +21,7 @@ Optional parameters:
- page
- tmax
- test
+- video
# Test Parameters
```
diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js
index 96616bb6a48..4cc648a2e04 100644
--- a/modules/synacormediaBidAdapter.js
+++ b/modules/synacormediaBidAdapter.js
@@ -1,9 +1,9 @@
'use strict';
-import { getAdUnitSizes, logWarn, deepSetValue, isFn, isPlainObject } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {deepSetValue, getAdUnitSizes, isFn, isPlainObject, logWarn} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {includes} from '../src/polyfill.js';
import {config} from '../src/config.js';
const BID_SCHEME = 'https://';
@@ -14,12 +14,7 @@ const BLOCKED_AD_SIZES = [
'1x1',
'1x2'
];
-const SUPPORTED_USER_ID_SOURCES = [
- 'liveramp.com', // Liveramp IdentityLink
- 'nextroll.com', // NextRoll XID
- 'verizonmedia.com', // Verizon Media ConnectID
- 'pubcid.org' // PubCommon ID
-];
+const DEFAULT_MAX_TTL = 420; // 7 minutes
export const spec = {
code: 'synacormedia',
supportedMediaTypes: [ BANNER, VIDEO ],
@@ -95,7 +90,7 @@ export const spec = {
// User ID
if (validBidReqs[0] && validBidReqs[0].userIdAsEids && Array.isArray(validBidReqs[0].userIdAsEids)) {
- const eids = this.processEids(validBidReqs[0].userIdAsEids);
+ const eids = validBidReqs[0].userIdAsEids;
if (eids.length) {
deepSetValue(openRtbBidRequest, 'user.ext.eids', eids);
}
@@ -114,16 +109,6 @@ export const spec = {
}
},
- processEids: function(userIdAsEids) {
- const eids = [];
- userIdAsEids.forEach(function(eid) {
- if (SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) > -1) {
- eids.push(eid);
- }
- });
- return eids;
- },
-
buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey) {
let format = [];
let imps = [];
@@ -248,6 +233,19 @@ export const spec = {
}
});
}
+
+ let maxTtl = DEFAULT_MAX_TTL;
+ if (bid.ext && bid.ext['imds.tv'] && bid.ext['imds.tv'].ttl) {
+ const bidTtlMax = parseInt(bid.ext['imds.tv'].ttl, 10);
+ maxTtl = !isNaN(bidTtlMax) && bidTtlMax > 0 ? bidTtlMax : DEFAULT_MAX_TTL;
+ }
+
+ let ttl = maxTtl;
+ if (bid.exp) {
+ const bidTtl = parseInt(bid.exp, 10);
+ ttl = !isNaN(bidTtl) && bidTtl > 0 ? Math.min(bidTtl, maxTtl) : maxTtl;
+ }
+
const bidObj = {
requestId: impid,
cpm: parseFloat(bid.price),
@@ -258,7 +256,7 @@ export const spec = {
netRevenue: true,
mediaType: isVideo ? VIDEO : BANNER,
ad: creative,
- ttl: 60
+ ttl,
};
if (bid.adomain != undefined || bid.adomain != null) {
diff --git a/modules/talkadsBidAdapter.js b/modules/talkadsBidAdapter.js
index f95456b5c54..dae452b9a7d 100644
--- a/modules/talkadsBidAdapter.js
+++ b/modules/talkadsBidAdapter.js
@@ -5,11 +5,12 @@ import {ajax} from '../src/ajax.js';
const CURRENCY = 'EUR';
const BIDDER_CODE = 'talkads';
+const GVLID = 1074;
export const spec = {
code: BIDDER_CODE,
+ gvlid: GVLID,
supportedMediaTypes: [ NATIVE, BANNER ],
- params: null,
/**
* Determines whether or not the given bid request is valid.
@@ -17,7 +18,7 @@ export const spec = {
* @param poBid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
- isBidRequestValid: (poBid) => {
+ isBidRequestValid: function (poBid) {
utils.logInfo('isBidRequestValid : ', poBid);
if (poBid.params === undefined) {
utils.logError('VALIDATION FAILED : the parameters must be defined');
@@ -31,7 +32,7 @@ export const spec = {
utils.logError('VALIDATION FAILED : the parameter "bidder_url" must be defined');
return false;
}
- this.params = poBid.params;
+
return !!(poBid.nativeParams || poBid.sizes);
}, // isBidRequestValid
@@ -42,7 +43,7 @@ export const spec = {
* @param poBidderRequest
* @return ServerRequest Info describing the request to the server.
*/
- buildRequests: (paValidBidRequests, poBidderRequest) => {
+ buildRequests: function (paValidBidRequests, poBidderRequest) {
utils.logInfo('buildRequests : ', paValidBidRequests, poBidderRequest);
const laBids = paValidBidRequests.map((poBid, piId) => {
const loOne = { id: piId, ad_unit: poBid.adUnitCode, bid_id: poBid.bidId, type: '', size: [] };
@@ -54,6 +55,7 @@ export const spec = {
}
return loOne;
});
+ let laParams = paValidBidRequests[0].params;
const loServerRequest = {
cur: CURRENCY,
timeout: poBidderRequest.timeout,
@@ -71,7 +73,7 @@ export const spec = {
loServerRequest.gdpr.consent = poBidderRequest.gdprConsent.consentString;
}
}
- const lsUrl = this.params.bidder_url + '/' + this.params.tag_id;
+ const lsUrl = laParams.bidder_url + '/' + laParams.tag_id;
return {
method: 'POST',
url: lsUrl,
@@ -86,7 +88,7 @@ export const spec = {
* @param poPidRequest Request original server request
* @return An array of bids which were nested inside the server.
*/
- interpretResponse: (poServerResponse, poPidRequest) => {
+ interpretResponse: function (poServerResponse, poPidRequest) {
utils.logInfo('interpretResponse : ', poServerResponse);
if (!poServerResponse.body) {
return [];
@@ -118,10 +120,11 @@ export const spec = {
*
* @param poBid The bid that won the auction
*/
- onBidWon: (poBid) => {
+ onBidWon: function (poBid) {
utils.logInfo('onBidWon : ', poBid);
+ let laParams = poBid.params[0];
if (poBid.pbid) {
- ajax(this.params.bidder_url + 'won/' + poBid.pbid);
+ ajax(laParams.bidder_url + 'won/' + poBid.pbid);
}
}, // onBidWon
};
diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js
index 5714916c131..4deb63b6426 100644
--- a/modules/targetVideoBidAdapter.js
+++ b/modules/targetVideoBidAdapter.js
@@ -1,16 +1,18 @@
-import find from 'core-js-pure/features/array/find.js';
-import { getBidRequest } from '../src/utils.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
+import {find} from '../src/polyfill.js';
+import {getBidRequest} from '../src/utils.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
const SOURCE = 'pbjs';
const BIDDER_CODE = 'targetVideo';
const ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid';
const MARGIN = 1.35;
+const GVLID = 786;
export const spec = {
code: BIDDER_CODE,
+ gvlid: GVLID,
supportedMediaTypes: [BANNER],
/**
diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js
index 4bea8858d98..a8902c896f6 100644
--- a/modules/teadsBidAdapter.js
+++ b/modules/teadsBidAdapter.js
@@ -12,7 +12,7 @@ const gdprStatus = {
CMP_NOT_FOUND_OR_ERROR: 22
};
const FP_TEADS_ID_COOKIE_NAME = '_tfpvi';
-export const storage = getStorageManager(GVL_ID, BIDDER_CODE);
+export const storage = getStorageManager({gvlid: GVL_ID, bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js
index 50a6ccc9fb2..42913414cbc 100644
--- a/modules/telariaBidAdapter.js
+++ b/modules/telariaBidAdapter.js
@@ -9,7 +9,11 @@ const EVENTS_ENDPOINT = `events.${DOMAIN}/diag`;
export const spec = {
code: BIDDER_CODE,
- aliases: ['tremor', 'tremorvideo'],
+ gvlid: 52,
+ aliases: [
+ { code: 'tremor', gvlid: 52 },
+ { code: 'tremorvideo', gvlid: 52 }
+ ],
supportedMediaTypes: [VIDEO],
/**
* Determines if the request is valid
diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js
index 006357cd4b9..88e89bcd64b 100644
--- a/modules/tpmnBidAdapter.js
+++ b/modules/tpmnBidAdapter.js
@@ -1,13 +1,16 @@
/* eslint-disable no-tabs */
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { parseUrl, deepAccess } from '../src/utils.js';
+import { getStorageManager } from '../src/storageManager.js';
import { BANNER } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
export const ADAPTER_VERSION = '1';
const SUPPORTED_AD_TYPES = [BANNER];
-
const BIDDER_CODE = 'tpmn';
const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn';
+const IFRAMESYNC = 'https://ad.tpmn.co.kr/sync.tpmn?type=iframe';
+export const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
@@ -18,20 +21,20 @@ export const spec = {
* @param {object} bid The bid to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
- isBidRequestValid: function(bid) {
+ isBidRequestValid: function (bid) {
return 'params' in bid &&
- 'inventoryId' in bid.params &&
- 'publisherId' in bid.params &&
- !isNaN(Number(bid.params.inventoryId)) &&
- bid.params.inventoryId > 0 &&
- (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes
+ 'inventoryId' in bid.params &&
+ 'publisherId' in bid.params &&
+ !isNaN(Number(bid.params.inventoryId)) &&
+ bid.params.inventoryId > 0 &&
+ (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes
},
/**
- * @param {BidRequest[]} bidRequests
- * @param {*} bidderRequest
- * @return {ServerRequest}
- */
+ * @param {BidRequest[]} bidRequests
+ * @param {*} bidderRequest
+ * @return {ServerRequest}
+ */
buildRequests: (bidRequests, bidderRequest) => {
if (bidRequests.length === 0) {
return [];
@@ -49,11 +52,11 @@ export const spec = {
}];
},
/**
- * 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.
- */
+ * 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, serverRequest) {
if (!Array.isArray(serverResponse.body)) {
return [];
@@ -63,7 +66,48 @@ export const spec = {
// our server directly returns the format needed by prebid.js so no more
// transformation is needed here.
return bidResults;
- }
+ },
+
+ getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
+ const syncArr = [];
+ if (syncOptions.iframeEnabled) {
+ let policyParam = '';
+ if (gdprConsent && gdprConsent.consentString) {
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
+ } else {
+ policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`;
+ }
+ }
+ if (uspConsent && uspConsent.consentString) {
+ policyParam += `&ccpa_consent=${uspConsent.consentString}`;
+ }
+ const coppa = config.getConfig('coppa') ? 1 : 0;
+ policyParam += `&coppa=${coppa}`;
+ syncArr.push({
+ type: 'iframe',
+ url: IFRAMESYNC + policyParam
+ })
+ } else {
+ syncArr.push({
+ type: 'image',
+ url: 'https://x.bidswitch.net/sync?ssp=tpmn'
+ });
+ syncArr.push({
+ type: 'image',
+ url: 'https://gocm.c.appier.net/tpmn'
+ });
+ syncArr.push({
+ type: 'image',
+ url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D'
+ });
+ syncArr.push({
+ type: 'image',
+ url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId'
+ });
+ }
+ return syncArr;
+ },
};
registerBidder(spec);
diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js
index dd1624f90d7..5750406116b 100644
--- a/modules/trionBidAdapter.js
+++ b/modules/trionBidAdapter.js
@@ -2,12 +2,11 @@ import { getBidIdParameter, parseSizesInput, tryAppendQueryString } from '../src
import {registerBidder} from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
-const storage = getStorageManager();
-
const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest';
const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.html';
const BIDDER_CODE = 'trion';
const BASE_KEY = '_trion_';
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js
index 215769e9812..7e964661db6 100644
--- a/modules/tripleliftBidAdapter.js
+++ b/modules/tripleliftBidAdapter.js
@@ -1,4 +1,4 @@
-import { tryAppendQueryString, logMessage, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js';
+import { tryAppendQueryString, logMessage, logError, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
@@ -175,14 +175,18 @@ function _getORTBVideo(bidRequest) {
function _getFloor (bid) {
let floor = null;
if (typeof bid.getFloor === 'function') {
- const floorInfo = bid.getFloor({
- currency: 'USD',
- mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner',
- size: '*'
- });
- if (typeof floorInfo === 'object' &&
- floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
- floor = parseFloat(floorInfo.floor);
+ try {
+ const floorInfo = bid.getFloor({
+ currency: 'USD',
+ mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner',
+ size: '*'
+ });
+ if (typeof floorInfo === 'object' &&
+ floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
+ floor = parseFloat(floorInfo.floor);
+ }
+ } catch (err) {
+ logError('Triplelift: getFloor threw an error: ', err);
}
}
return floor !== null ? floor : bid.params.floor;
@@ -192,9 +196,9 @@ function _getGlobalFpd() {
const fpd = {};
const context = {}
const user = {};
- const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {};
+ const ortbData = config.getConfig('ortb2') || {};
- const fpdContext = Object.assign({}, ortbData.context);
+ const fpdContext = Object.assign({}, ortbData.site);
const fpdUser = Object.assign({}, ortbData.user);
_addEntries(context, fpdContext);
diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js
index 74b522e8f1a..27ca4bf6340 100644
--- a/modules/trustpidSystem.js
+++ b/modules/trustpidSystem.js
@@ -4,7 +4,7 @@
* @module modules/trustpidSystem
* @requires module:modules/userId
*/
-import { logInfo, logError } from '../src/utils.js';
+import { logInfo } from '../src/utils.js';
import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
@@ -13,7 +13,7 @@ const LOG_PREFIX = 'Trustpid module'
let mnoAcronym = '';
let mnoDomain = '';
-export const storage = getStorageManager(null, MODULE_NAME);
+export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME});
/**
* Handle an event for an iframe.
@@ -22,10 +22,9 @@ export const storage = getStorageManager(null, MODULE_NAME);
* @param event
*/
function messageHandler(event) {
- let msg;
try {
- if (event && event.data && typeof event.data === 'string' && event.data) {
- msg = JSON.parse(event.data);
+ if (event && event.data && typeof event.data === 'string') {
+ const msg = JSON.parse(event.data);
if (msg.msgType === 'MNOSELECTOR' && msg.body && msg.body.url) {
let URL = msg.body.url.split('//');
let domainURL = URL[1].split('/');
@@ -35,7 +34,7 @@ function messageHandler(event) {
}
}
} catch (e) {
- logError(e);
+ logInfo(`${LOG_PREFIX}: Unsupported message caught. Origin: ${event.origin}, data: ${event.data}.`);
}
}
@@ -44,31 +43,13 @@ function messageHandler(event) {
* @param domain
*/
function getDomainAcronym(domain) {
- let acronym = '';
const prefix = '-';
- switch (domain) {
- case 'tmi.mno.link':
- acronym = 'ndye';
- break;
- case 'tmi.vodafone.de':
- acronym = 'pqnx';
- break;
- case 'tmi.telekom.de':
- acronym = 'avgw';
- break;
- case 'tmi.tmid.es':
- acronym = 'kjws';
- break;
- case 'uat.mno.link':
- acronym = 'xxxx';
- break;
- case 'es.tmiservice.orange.com':
- acronym = 'aplw';
- break;
- default:
- return 'none';
+ const acronym = window.FC_CONF?.TELCO_ACRONYM?.[domain];
+ if (!acronym) {
+ logInfo(`${LOG_PREFIX}: No acronym found for domain: ${domain}`);
+ return;
}
- return mnoAcronym = prefix + acronym;
+ mnoAcronym = prefix + acronym;
}
// Set a listener to handle the iframe response message.
diff --git a/modules/trustpidSystem.md b/modules/trustpidSystem.md
index c4309c9d807..c1ad1ab567b 100644
--- a/modules/trustpidSystem.md
+++ b/modules/trustpidSystem.md
@@ -5,41 +5,18 @@ trustpid User Id Module.
First, make sure to add the trustpid submodule to your Prebid.js package with:
```
-gulp build --modules=userId,adfBidAdapter,trustpidSystem
-```
-
-The following configuration parameters are available:
-
-```
-pbjs.setConfig({
- userSync: {
- userIds: [
- {
- name: 'trustpid',
- params: {
- maxDelayTime: 1000,
- },
- bidders: ["adf"],
- storage: {
- type: "html5",
- name: "trustpid",
- expires: 1, //days
- },
- }
- ],
- }
-});
+gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,trustpidSystem
```
## Parameter Descriptions
-| Param under userSync.userIds[] | Scope | Type | Description | Example |
-| --- | --- | --- | --- | --- |
-| name | Required | String | The name of the module | `"trustpid"`
-| params | Required | Object | Object with configuration parameters for trustpid User Id submodule | - |
-| params.maxDelayTime | Required | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 |
-| bidders | Required | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | `["adf"]` |
-| storage | Required | Object | Local storage configuration object | - |
-| storage.type | Required | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` |
-| storage.name | Required | String | The name of the key in local storage where the user ID will be stored. | `"trustpid"` |
-| storage.expires | Required | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required.| `1` |
\ No newline at end of file
+| Params under userSync.userIds[] | Type | Description | Example |
+| --- | --- | --- | --- |
+| name | String | The name of the module | `"trustpid"` |
+| params | Object | Object with configuration parameters for trustpid User Id submodule | - |
+| params.maxDelayTime | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 |
+| bidders | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | [`"adf"`, `"adformPBS"`, `"ix"`] |
+| storage | Object | Local storage configuration object | - |
+| storage.type | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` |
+| storage.name | String | The name of the key in local storage where the user ID will be stored. | `"trustpid"` |
+| storage.expires | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required.| `1` |
\ No newline at end of file
diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js
index 70cae0f50f1..7d40a0b0452 100644
--- a/modules/trustxBidAdapter.js
+++ b/modules/trustxBidAdapter.js
@@ -1,4 +1,4 @@
-import { isEmpty, deepAccess, logError, logWarn, parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js';
+import {isEmpty, deepAccess, logError, logWarn, parseGPTSingleSizeArrayToRtbSize, mergeDeep} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import { Renderer } from '../src/Renderer.js';
import { VIDEO, BANNER } from '../src/mediaTypes.js';
@@ -21,6 +21,7 @@ const LOG_ERROR_MESS = {
hasEmptySeatbidArray: 'Response has empty seatbid array',
hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - '
};
+
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [ BANNER, VIDEO ],
@@ -173,16 +174,25 @@ export const spec = {
request.site.content = content;
}
- const userData = [];
- addSegments('iow_labs_pub_data', 'jwpseg', jwpseg, userData);
- addSegments('permutive', 'p_standard', permutiveseg, userData, 'permutive.com');
-
- if (userData.length) {
+ if (jwpseg && jwpseg.length) {
user = {
- data: userData
+ data: [{
+ name: 'iow_labs_pub_data',
+ segment: segmentProcessing(jwpseg, 'jwpseg'),
+ }]
};
}
+ const ortb2UserData = config.getConfig('ortb2.user.data');
+ if (ortb2UserData && ortb2UserData.length) {
+ if (!user) {
+ user = { data: [] };
+ }
+ user = mergeDeep(user, {
+ data: [...ortb2UserData]
+ });
+ }
+
if (gdprConsent && gdprConsent.consentString) {
userExt = {consent: gdprConsent.consentString};
}
@@ -431,34 +441,20 @@ function createBannerRequest(bid, mediaType) {
return result;
}
-function addSegments(name, segName, segments, data, bidConfigName) {
- if (segments && segments.length) {
- data.push({
- name: name,
- segment: segments
- .map((seg) => seg && (seg.id || seg))
- .filter((seg) => seg && (typeof seg === 'string' || typeof seg === 'number'))
- .map((seg) => ({ name: segName, value: seg.toString() }))
- });
- } else if (bidConfigName) {
- const configData = config.getConfig('ortb2.user.data');
- let segData = null;
- configData && configData.some(({name, segment}) => {
- if (name === bidConfigName) {
- segData = segment;
- return true;
+function segmentProcessing(segment, forceSegName) {
+ return segment
+ .map((seg) => {
+ const value = seg && (seg.value || seg.id || seg);
+ if (typeof value === 'string' || typeof value === 'number') {
+ return {
+ value: value.toString(),
+ ...(forceSegName && { name: forceSegName }),
+ ...(seg.name && { name: seg.name }),
+ };
}
- });
- if (segData && segData.length) {
- data.push({
- name: name,
- segment: segData
- .map((seg) => seg && (seg.id || seg))
- .filter((seg) => seg && (typeof seg === 'string' || typeof seg === 'number'))
- .map((seg) => ({ name: segName, value: seg.toString() }))
- });
- }
- }
+ return null;
+ })
+ .filter((seg) => !!seg);
}
function reformatKeywords(pageKeywords) {
diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js
new file mode 100644
index 00000000000..4919442336f
--- /dev/null
+++ b/modules/ttdBidAdapter.js
@@ -0,0 +1,519 @@
+import * as utils from '../src/utils.js';
+import { config } from '../src/config.js';
+import { createEidsArray } from './userId/eids.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+
+const BIDADAPTERVERSION = 'TTD-PREBID-2022.02.18';
+const BIDDER_CODE = 'ttd';
+const BIDDER_CODE_LONG = 'thetradedesk';
+const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/';
+const USER_SYNC_ENDPOINT = 'https://match.adsrvr.org';
+
+const MEDIA_TYPE = {
+ BANNER: 1,
+ VIDEO: 2
+};
+
+function getExt(firstPartyData) {
+ const ext = {
+ ver: BIDADAPTERVERSION,
+ pbjs: '$prebid.version$',
+ keywords: firstPartyData.site?.keywords ? firstPartyData.site.keywords.split(',').map(k => k.trim()) : []
+ }
+ return {
+ ttdprebid: ext
+ };
+}
+
+function getRegs(bidderRequest) {
+ let regs = {};
+
+ if (bidderRequest.gdprConsent && typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
+ utils.deepSetValue(regs, 'ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0);
+ }
+ if (bidderRequest.uspConsent) {
+ utils.deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent);
+ }
+ if (config.getConfig('coppa') === true) {
+ regs.coppa = 1;
+ }
+ return regs;
+}
+
+function getBidFloor(bid) {
+ if (!utils.isFn(bid.getFloor)) {
+ return null;
+ }
+
+ let floor = bid.getFloor({
+ currency: 'USD',
+ mediaType: '*',
+ size: '*'
+ });
+ if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
+ return floor.floor;
+ }
+ return null;
+}
+
+function getSource(validBidRequests) {
+ let source = {};
+ if (validBidRequests[0].schain) {
+ utils.deepSetValue(source, 'ext.schain', validBidRequests[0].schain);
+ }
+ return source;
+}
+
+function getDevice() {
+ const language = navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage;
+ let device = {
+ ua: navigator.userAgent,
+ dnt: utils.getDNT() ? 1 : 0,
+ language: language,
+ connectiontype: getConnectionType()
+ };
+
+ return device;
+};
+
+function getConnectionType() {
+ const connection = navigator.connection || navigator.webkitConnection;
+ if (!connection) {
+ return 0;
+ }
+ switch (connection.type) {
+ case 'ethernet':
+ return 1;
+ case 'wifi':
+ return 2;
+ case 'cellular':
+ switch (connection.effectiveType) {
+ case 'slow-2g':
+ case '2g':
+ return 4;
+ case '3g':
+ return 5;
+ case '4g':
+ return 6;
+ default:
+ return 3;
+ }
+ default:
+ return 0;
+ }
+}
+
+function getUser(bidderRequest) {
+ let user = {};
+ if (bidderRequest.gdprConsent) {
+ utils.deepSetValue(user, 'ext.consent', bidderRequest.gdprConsent.consentString);
+ }
+
+ if (utils.isStr(utils.deepAccess(bidderRequest, 'bids.0.userId.tdid'))) {
+ user.buyeruid = bidderRequest.bids[0].userId.tdid;
+ }
+
+ var eids = createEidsArray(utils.deepAccess(bidderRequest, 'bids.0.userId'))
+ if (eids.length) {
+ utils.deepSetValue(user, 'ext.eids', eids);
+ }
+
+ return user;
+}
+
+function getSite(bidderRequest, firstPartyData) {
+ var site = {
+ id: utils.deepAccess(bidderRequest, 'bids.0.params.siteId'),
+ page: utils.deepAccess(bidderRequest, 'refererInfo.referer'),
+ publisher: {
+ id: utils.deepAccess(bidderRequest, 'bids.0.params.publisherId'),
+ },
+ ...firstPartyData.site
+ };
+
+ var publisherDomain = config.getConfig('publisherDomain');
+ if (publisherDomain) {
+ utils.deepSetValue(site, 'publisher.domain', publisherDomain);
+ }
+ return site;
+}
+
+function getImpression(bidRequest) {
+ let impression = {
+ id: bidRequest.bidId,
+ tagid: bidRequest.params.placementId
+ };
+
+ let gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.gpid');
+ if (gpid) {
+ impression.ext = {
+ gpid: gpid
+ }
+ }
+
+ const mediaTypesVideo = utils.deepAccess(bidRequest, 'mediaTypes.video');
+ const mediaTypesBanner = utils.deepAccess(bidRequest, 'mediaTypes.banner');
+
+ let mediaTypes = {};
+ if (mediaTypesBanner) {
+ mediaTypes[BANNER] = banner(bidRequest);
+ }
+ if (mediaTypesVideo) {
+ mediaTypes[VIDEO] = video(bidRequest);
+ }
+
+ Object.assign(impression, mediaTypes);
+
+ let bidfloor = getBidFloor(bidRequest);
+ if (bidfloor) {
+ impression.bidfloor = parseFloat(bidfloor);
+ impression.bidfloorcur = 'USD';
+ }
+
+ return impression;
+}
+
+function getSizes(sizes) {
+ const sizeStructs = utils.parseSizesInput(sizes)
+ .filter(x => x) // sizes that don't conform are returned as null, which we want to ignore
+ .map(x => x.split('x'))
+ .map(size => {
+ return {
+ width: parseInt(size[0]),
+ height: parseInt(size[1]),
+ }
+ });
+
+ return sizeStructs;
+}
+
+function banner(bid) {
+ const sizes = getSizes(bid.mediaTypes.banner.sizes).map(x => {
+ return {
+ w: x.width,
+ h: x.height,
+ }
+ });
+ const pos = parseInt(utils.deepAccess(bid, 'mediaTypes.banner.pos'));
+ const expdir = utils.deepAccess(bid, 'params.banner.expdir');
+ let optionalParams = {};
+ if (pos) {
+ optionalParams.pos = pos;
+ }
+ if (expdir && Array.isArray(expdir)) {
+ optionalParams.expdir = expdir;
+ }
+
+ const banner = Object.assign(
+ {
+ w: sizes[0].w,
+ h: sizes[0].h,
+ format: sizes,
+ },
+ optionalParams);
+ return banner;
+}
+
+function video(bid) {
+ let minduration = utils.deepAccess(bid, 'mediaTypes.video.minduration');
+ const maxduration = utils.deepAccess(bid, 'mediaTypes.video.maxduration');
+ const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize');
+ const api = utils.deepAccess(bid, 'mediaTypes.video.api');
+ const mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes');
+ const placement = utils.deepAccess(bid, 'mediaTypes.video.placement');
+ const protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols');
+ const playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod');
+ const pos = utils.deepAccess(bid, 'mediaTypes.video.pos');
+ const startdelay = utils.deepAccess(bid, 'mediaTypes.video.startdelay');
+ const skip = utils.deepAccess(bid, 'mediaTypes.video.skip');
+ const skipmin = utils.deepAccess(bid, 'mediaTypes.video.skipmin');
+ const skipafter = utils.deepAccess(bid, 'mediaTypes.video.skipafter');
+ const minbitrate = utils.deepAccess(bid, 'mediaTypes.video.minbitrate');
+ const maxbitrate = utils.deepAccess(bid, 'mediaTypes.video.maxbitrate');
+
+ if (!minduration || !utils.isInteger(minduration)) {
+ minduration = 0
+ }
+ let video = {
+ minduration: minduration,
+ maxduration: maxduration,
+ api: api,
+ mimes: mimes,
+ placement: placement,
+ protocols: protocols
+ };
+
+ if (typeof playerSize !== 'undefined') {
+ if (utils.isArray(playerSize[0])) {
+ video.w = parseInt(playerSize[0][0]);
+ video.h = parseInt(playerSize[0][1]);
+ } else if (utils.isNumber(playerSize[0])) {
+ video.w = parseInt(playerSize[0]);
+ video.h = parseInt(playerSize[1]);
+ }
+ }
+
+ if (playbackmethod) {
+ video.playbackmethod = playbackmethod;
+ }
+ if (pos) {
+ video.pos = pos;
+ }
+ if (startdelay && utils.isInteger(startdelay)) {
+ video.startdelay = startdelay;
+ }
+ if (skip && (skip === 0 || skip === 1)) {
+ video.skip = skip;
+ }
+ if (skipmin && utils.isInteger(skipmin)) {
+ video.skipmin = skipmin;
+ }
+ if (skipafter && utils.isInteger(skipafter)) {
+ video.skipafter = skipafter;
+ }
+ if (minbitrate && utils.isInteger(minbitrate)) {
+ video.minbitrate = minbitrate;
+ }
+ if (maxbitrate && utils.isInteger(maxbitrate)) {
+ video.maxbitrate = maxbitrate;
+ }
+
+ return video;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: 21,
+ aliases: [BIDDER_CODE_LONG],
+ 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) {
+ const alphaRegex = /^[\w+]+$/;
+
+ // required parameters
+ if (!bid || !bid.params) {
+ utils.logWarn(BIDDER_CODE + ': Missing bid parameters');
+ return false;
+ }
+ if (!bid.params.supplySourceId) {
+ utils.logWarn(BIDDER_CODE + ': Missing required parameter params.supplySourceId');
+ return false;
+ }
+ if (!alphaRegex.test(bid.params.supplySourceId)) {
+ utils.logWarn(BIDDER_CODE + ': supplySourceId must only contain alphabetic characters');
+ return false;
+ }
+ if (!bid.params.publisherId) {
+ utils.logWarn(BIDDER_CODE + ': Missing required parameter params.publisherId');
+ return false;
+ }
+ if (bid.params.publisherId.length > 32) {
+ utils.logWarn(BIDDER_CODE + ': params.publisherId must be 32 characters or less');
+ return false;
+ }
+ if (!bid.params.siteId) {
+ utils.logWarn(BIDDER_CODE + ': Missing required parameter params.siteId');
+ return false;
+ }
+ if (bid.params.siteId.length > 50) {
+ utils.logWarn(BIDDER_CODE + ': params.siteId must be 50 characters or less');
+ return false;
+ }
+ if (!bid.params.placementId) {
+ utils.logWarn(BIDDER_CODE + ': Missing required parameter params.placementId');
+ return false;
+ }
+ if (bid.params.placementId.length > 128) {
+ utils.logWarn(BIDDER_CODE + ': params.placementId must be 128 characters or less');
+ return false;
+ }
+
+ const mediaTypesBanner = utils.deepAccess(bid, 'mediaTypes.banner');
+ const mediaTypesVideo = utils.deepAccess(bid, 'mediaTypes.video');
+
+ if (!mediaTypesBanner && !mediaTypesVideo) {
+ utils.logWarn(BIDDER_CODE + ': one of mediaTypes.banner or mediaTypes.video must be passed');
+ return false;
+ }
+
+ if (mediaTypesVideo) {
+ if (!mediaTypesVideo.maxduration || !utils.isInteger(mediaTypesVideo.maxduration)) {
+ utils.logWarn(BIDDER_CODE + ': mediaTypes.video.maxduration must be set to the maximum video ad duration in seconds');
+ return false;
+ }
+ if (!mediaTypesVideo.api || mediaTypesVideo.api.length === 0) {
+ utils.logWarn(BIDDER_CODE + ': mediaTypes.video.api should be an array of supported api frameworks. See the Open RTB v2.5 spec for valid values');
+ return false;
+ }
+ if (!mediaTypesVideo.mimes || mediaTypesVideo.mimes.length === 0) {
+ utils.logWarn(BIDDER_CODE + ': mediaTypes.video.mimes should be an array of supported mime types');
+ return false;
+ }
+ if (!mediaTypesVideo.protocols) {
+ utils.logWarn(BIDDER_CODE + ': mediaTypes.video.protocols should be an array of supported protocols. See the Open RTB v2.5 spec for valid values')
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {BidRequest[]} an array of validBidRequests
+ * @param {*} bidderRequest
+ * @return {ServerRequest} Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests, bidderRequest) {
+ const firstPartyData = config.getConfig('ortb2') || {};
+ let topLevel = {
+ id: bidderRequest.auctionId,
+ imp: validBidRequests.map(bidRequest => getImpression(bidRequest)),
+ site: getSite(bidderRequest, firstPartyData),
+ device: getDevice(),
+ user: getUser(bidderRequest),
+ at: 1,
+ cur: ['USD'],
+ regs: getRegs(bidderRequest),
+ source: getSource(validBidRequests),
+ ext: getExt(firstPartyData)
+ }
+
+ let url = BIDDER_ENDPOINT + bidderRequest.bids[0].params.supplySourceId;
+
+ let serverRequest = {
+ method: 'POST',
+ url: url,
+ data: topLevel,
+ options: {
+ withCredentials: true
+ }
+ };
+
+ return serverRequest;
+ },
+
+ /**
+ * Format responses as Prebid bid responses
+ *
+ * Each bid can have the following elements:
+ * - requestId (required)
+ * - cpm (required)
+ * - width (required)
+ * - height (required)
+ * - ad (required)
+ * - ttl (required)
+ * - creativeId (required)
+ * - netRevenue (required)
+ * - currency (required)
+ * - vastUrl
+ * - vastImpUrl
+ * - vastXml
+ * - dealId
+ *
+ * @param {ttdResponseObj} bidResponse A successful response from ttd.
+ * @param {ServerRequest} serverRequest The result of buildRequests() that lead to this response.
+ * @return {Bid[]} An array of formatted bids.
+ */
+ interpretResponse: function (response, serverRequest) {
+ let seatBidsInResponse = utils.deepAccess(response, 'body.seatbid');
+ const currency = utils.deepAccess(response, 'body.cur');
+ if (!seatBidsInResponse || seatBidsInResponse.length === 0) {
+ return [];
+ }
+ let bidResponses = [];
+ let requestedImpressions = utils.deepAccess(serverRequest, 'data.imp');
+
+ seatBidsInResponse.forEach(seatBid => {
+ seatBid.bid.forEach(bid => {
+ let matchingRequestedImpression = requestedImpressions.find(imp => imp.id === bid.impid);
+
+ const cpm = bid.price || 0;
+ let bidResponse = {
+ requestId: bid.impid,
+ cpm: cpm,
+ creativeId: bid.crid,
+ dealId: bid.dealid || null,
+ currency: currency || 'USD',
+ netRevenue: true,
+ ttl: bid.ttl || 360,
+ meta: {},
+ };
+
+ if (bid.adomain && bid.adomain.length > 0) {
+ bidResponse.meta.advertiserDomains = bid.adomain;
+ }
+
+ if (bid.ext.mediatype === MEDIA_TYPE.BANNER) {
+ Object.assign(
+ bidResponse,
+ {
+ width: bid.w,
+ height: bid.h,
+ ad: utils.replaceAuctionPrice(bid.adm, cpm),
+ mediaType: BANNER
+ }
+ );
+ } else if (bid.ext.mediatype === MEDIA_TYPE.VIDEO) {
+ Object.assign(
+ bidResponse,
+ {
+ width: matchingRequestedImpression.video.w,
+ height: matchingRequestedImpression.video.h,
+ mediaType: VIDEO
+ }
+ );
+ if (bid.nurl) {
+ bidResponse.vastUrl = utils.replaceAuctionPrice(bid.nurl, cpm);
+ } else {
+ bidResponse.vastXml = utils.replaceAuctionPrice(bid.adm, cpm);
+ }
+ }
+
+ bidResponses.push(bidResponse);
+ });
+ });
+
+ return bidResponses;
+ },
+
+ /**
+ * 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.
+ * @param {gdprConsent} gdprConsent GDPR consent object
+ * @param {uspConsent} uspConsent USP consent object
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') {
+ const syncs = [];
+
+ let gdprParams = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString)}`;
+
+ let url = `${USER_SYNC_ENDPOINT}/track/usersync?us_privacy=${encodeURIComponent(uspConsent)}${gdprParams}`;
+
+ if (syncOptions.pixelEnabled) {
+ syncs.push({
+ type: 'image',
+ url: url + '&ust=image'
+ });
+ } else if (syncOptions.iframeEnabled) {
+ syncs.push({
+ type: 'iframe',
+ url: url + '&ust=iframe'
+ });
+ }
+ return syncs;
+ },
+};
+
+registerBidder(spec)
diff --git a/modules/ttdBidAdapter.md b/modules/ttdBidAdapter.md
new file mode 100644
index 00000000000..9c67f3267cb
--- /dev/null
+++ b/modules/ttdBidAdapter.md
@@ -0,0 +1,122 @@
+# Overview
+
+```
+Module Name: The Trade Desk Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: prebid-maintainers@thetradedesk.com
+```
+
+# Description
+
+Module that connects to The Trade Desk's demand sources to fetch bids.
+
+The Trade Desk bid adapter supports Banner and Video.
+
+# Test Parameters
+
+```js
+ var adUnits = [
+ // Banner adUnit with only required parameters
+ {
+ code: 'test-div-minimal',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'ttd',
+ params: {
+ supplySourceId: 'supplier',
+ publisherId: '1427ab10f2e448057ed3b422',
+ siteId: 'site-123',
+ placementId: 'footer1'
+ }
+ }
+ ]
+ },
+ // Banner adUnit with all optional parameters provided
+ {
+ code: 'test-div-banner-optional-params',
+ mediaTypes: {
+ banner: {
+ sizes: [[728, 90]],
+ pos: 1
+ }
+ },
+ bids: [
+ {
+ bidder: 'ttd',
+ params: {
+ supplySourceId: 'supplier',
+ publisherId: '1427ab10f2e448057ed3b422',
+ siteId: 'site-123',
+ placementId: 'footer1',
+ banner: {
+ expdir: [1, 3]
+ },
+ }
+ }
+ ]
+ },
+ // Video adUnit with only required parameters
+ {
+ code: 'test-div-video-minimal',
+ mediaTypes: {
+ video: {
+ maxduration: 30,
+ api: [1, 3],
+ mimes: ['video/mp4'],
+ placement: 3,
+ protocols: [2,3,5,6]
+ }
+ },
+ bids: [
+ {
+ bidder: 'ttd',
+ params: {
+ supplySourceId: 'supplier',
+ publisherId: '1427ab10f2e448057ed3b422',
+ siteId: 'site-123',
+ placementId: 'footer1'
+ }
+ }
+ ]
+ },
+ // Video adUnit with all optional parameters provided
+ {
+ code: 'test-div-video-full',
+ mediaTypes: {
+ video: {
+ minduration: 1,
+ maxduration: 10,
+ playerSize: [640, 480],
+ api: [1, 3],
+ mimes: ['video/mp4'],
+ placement: 3,
+ protocols: [2, 3, 5, 6],
+ startdelay: 1,
+ playbackmethod: [1],
+ pos: 1,
+ minbitrate: 100,
+ maxbitrate: 500,
+ skip: 1,
+ skipmin: 5,
+ skipafter: 10
+ }
+ },
+ bids: [
+ {
+ bidder: 'ttd',
+ params: {
+ supplySourceId: 'supplier',
+ publisherId: '1427ab10f2e448057ed3b422',
+ siteId: 'site-123',
+ placementId: 'footer1'
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js
index 8b85f1ebad3..ec087d005d6 100644
--- a/modules/ucfunnelBidAdapter.js
+++ b/modules/ucfunnelBidAdapter.js
@@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js';
import { getStorageManager } from '../src/storageManager.js';
import { config } from '../src/config.js';
-const storage = getStorageManager();
+
const COOKIE_NAME = 'ucf_uid';
const VER = 'ADGENT_PREBID-2018011501';
const BIDDER_CODE = 'ucfunnel';
@@ -13,6 +13,7 @@ const VIDEO_CONTEXT = {
INSTREAM: 0,
OUSTREAM: 2
}
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
export const spec = {
code: BIDDER_CODE,
diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js
index c0cd9166784..23656639532 100644
--- a/modules/uid2IdSystem.js
+++ b/modules/uid2IdSystem.js
@@ -23,7 +23,7 @@ function readFromLocalStorage() {
}
function getStorage() {
- return getStorageManager(GVLID, MODULE_NAME);
+ return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME});
}
const storage = getStorage();
diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js
index 0209c808979..977e694acf7 100644
--- a/modules/unicornBidAdapter.js
+++ b/modules/unicornBidAdapter.js
@@ -3,12 +3,12 @@ import {BANNER} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {getStorageManager} from '../src/storageManager.js';
-const storage = getStorageManager();
const BIDDER_CODE = 'unicorn';
const UNICORN_ENDPOINT = 'https://ds.uncn.jp/pb/0/bid.json';
const UNICORN_DEFAULT_CURRENCY = 'JPY';
const UNICORN_PB_COOKIE_KEY = '__pb_unicorn_aud';
const UNICORN_PB_VERSION = '1.1';
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
/**
* Placement ID and Account ID are required.
diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js
index 99fbe63aeb4..77160bb01e6 100644
--- a/modules/unrulyBidAdapter.js
+++ b/modules/unrulyBidAdapter.js
@@ -193,6 +193,7 @@ const isBannerMediaTypeValid = (mediaTypeBannerData) => {
export const adapter = {
code: 'unruly',
supportedMediaTypes: [VIDEO, BANNER],
+ gvlid: 36,
isBidRequestValid: function (bid) {
let siteId = deepAccess(bid, 'params.siteId');
let isBidValid = siteId && isMediaTypesValid(bid);
diff --git a/modules/userId/eids.js b/modules/userId/eids.js
index e78cffdcbac..9c995a52fe3 100644
--- a/modules/userId/eids.js
+++ b/modules/userId/eids.js
@@ -1,7 +1,7 @@
import { pick, isFn, isStr, isPlainObject, deepAccess } from '../../src/utils.js';
// Each user-id sub-module is expected to mention respective config here
-const USER_IDS_CONFIG = {
+export const USER_IDS_CONFIG = {
// key-name : {config}
@@ -63,6 +63,20 @@ const USER_IDS_CONFIG = {
}
},
+ // ftrack
+ 'ftrackId': {
+ source: 'flashtalking.com',
+ atype: 1,
+ getValue: function(data) {
+ return data.uid
+ },
+ getUidExt: function(data) {
+ if (data.ext) {
+ return data.ext;
+ }
+ }
+ },
+
// parrableId
'parrableId': {
source: 'parrable.com',
@@ -285,6 +299,21 @@ const USER_IDS_CONFIG = {
source: 'adquery.io',
atype: 1
},
+
+ // DAC ID
+ 'dacId': {
+ source: 'impact-ad.jp',
+ atype: 1
+ },
+
+ // 33across ID
+ '33acrossId': {
+ source: '33across.com',
+ atype: 1,
+ getValue: function(data) {
+ return data.envelope;
+ }
+ },
};
// this function will create an eid object for the given UserId sub-module
diff --git a/modules/userId/eids.md b/modules/userId/eids.md
index 4c516d5441c..45237ac5f26 100644
--- a/modules/userId/eids.md
+++ b/modules/userId/eids.md
@@ -2,6 +2,13 @@
```
userIdAsEids = [
+ {
+ source: '33across.com',
+ uids: [{
+ id: 'some-random-id-value',
+ atype: 1
+ }]
+ },
{
source: 'trustpid.com',
uids: [{
@@ -65,6 +72,13 @@ userIdAsEids = [
}]
},
+ {
+ source: 'flashtalking.com',
+ uids: [{
+ id: 'some-random-id-value',
+ atype: 1
+ },
+
{
source: 'parrable.com',
uids: [{
diff --git a/modules/userId/index.js b/modules/userId/index.js
index 42fff2cd16c..809ca624748 100644
--- a/modules/userId/index.js
+++ b/modules/userId/index.js
@@ -125,25 +125,34 @@
* @property {(function|undefined)} callback - function that will return an id
*/
-/**
- * @typedef {Object} RefreshUserIdsOptions
- * @property {(string[]|undefined)} submoduleNames - submodules to refresh
- */
-
-import find from 'core-js-pure/features/array/find.js';
-import { config } from '../../src/config.js';
-import events from '../../src/events.js';
-import { getGlobal } from '../../src/prebidGlobal.js';
-import { gdprDataHandler } from '../../src/adapterManager.js';
+import {find, includes} from '../../src/polyfill.js';
+import {config} from '../../src/config.js';
+import * as events from '../../src/events.js';
+import {getGlobal} from '../../src/prebidGlobal.js';
+import {gdprDataHandler} from '../../src/adapterManager.js';
import CONSTANTS from '../../src/constants.json';
-import { module, hook } from '../../src/hook.js';
-import { createEidsArray, buildEidPermissions } from './eids.js';
-import { getCoreStorageManager } from '../../src/storageManager.js';
+import {hook, module, ready as hooksReady} from '../../src/hook.js';
+import {buildEidPermissions, createEidsArray, USER_IDS_CONFIG} from './eids.js';
+import {getCoreStorageManager} from '../../src/storageManager.js';
import {
- getPrebidInternal, isPlainObject, logError, isArray, cyrb53Hash, deepAccess, timestamp, delayExecution, logInfo, isFn,
- logWarn, isEmptyStr, isNumber, isGptPubadsDefined
+ cyrb53Hash,
+ deepAccess,
+ delayExecution,
+ getPrebidInternal,
+ isArray,
+ isEmptyStr,
+ isFn,
+ isGptPubadsDefined,
+ isNumber,
+ isPlainObject,
+ logError,
+ logInfo,
+ logWarn,
+ timestamp,
+ isEmpty
} from '../../src/utils.js';
-import includes from 'core-js-pure/features/array/includes.js';
+import {getPPID as coreGetPPID} from '../../src/adserver.js';
+import {promiseControls} from '../../src/utils/promise.js';
const MODULE_NAME = 'User ID';
const COOKIE = 'cookie';
@@ -187,6 +196,8 @@ export let auctionDelay;
/** @type {(string|undefined)} */
let ppidSource;
+let configListener;
+
/** @param {Submodule[]} submodules */
export function setSubmoduleRegistry(submodules) {
submoduleRegistry = submodules;
@@ -414,7 +425,7 @@ function processSubmoduleCallbacks(submodules, cb) {
}, submodules.length);
}
submodules.forEach(function (submodule) {
- submodule.callback(function callbackCompleted(idObj) {
+ function callbackCompleted(idObj) {
// if valid, id data should be saved to cookie/html storage
if (idObj) {
if (submodule.config.storage) {
@@ -426,8 +437,13 @@ function processSubmoduleCallbacks(submodules, cb) {
logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`);
}
done();
- });
-
+ }
+ try {
+ submodule.callback(callbackCompleted);
+ } catch (e) {
+ logError(`Error in userID module '${submodule.submodule.name}':`, e);
+ done();
+ }
// clear callback, this prop is used to test if all submodule callbacks are complete below
submodule.callback = undefined;
});
@@ -451,6 +467,20 @@ function getCombinedSubmoduleIds(submodules) {
return combinedSubmoduleIds;
}
+/**
+ * This function will return a submodule ID object for particular source name
+ * @param {SubmoduleContainer[]} submodules
+ * @param {string} sourceName
+ */
+function getSubmoduleId(submodules, sourceName) {
+ if (!Array.isArray(submodules) || !submodules.length) {
+ return {};
+ }
+ const submodule = submodules.filter(sub => isPlainObject(sub.idObj) &&
+ Object.keys(sub.idObj).length && USER_IDS_CONFIG[Object.keys(sub.idObj)[0]]?.source === sourceName);
+ return !isEmpty(submodule) ? submodule[0].idObj : [];
+}
+
/**
* This function will create a combined object for bidder with allowed subModule Ids
* @param {SubmoduleContainer[]} submodules
@@ -493,56 +523,111 @@ function addIdDataToAdUnitBids(adUnits, submodules) {
});
}
-/**
- * This is a common function that will initialize subModules if not already done and it will also execute subModule callbacks
- */
-function initializeSubmodulesAndExecuteCallbacks(continueAuction) {
- let delayed = false;
+function delayFor(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
- // initialize submodules only when undefined
- if (typeof initializedSubmodules === 'undefined') {
- initializedSubmodules = initSubmodules(submodules, gdprDataHandler.getConsentData());
- if (initializedSubmodules.length) {
- setPrebidServerEidPermissions(initializedSubmodules);
- // list of submodules that have callbacks that need to be executed
- const submodulesWithCallbacks = initializedSubmodules.filter(item => isFn(item.callback));
+const INIT_CANCELED = {};
- if (submodulesWithCallbacks.length) {
- if (continueAuction && auctionDelay > 0) {
- // delay auction until ids are available
- delayed = true;
- let continued = false;
- const continueCallback = function () {
- if (!continued) {
- continued = true;
- continueAuction();
- }
- }
- logInfo(`${MODULE_NAME} - auction delayed by ${auctionDelay} at most to fetch ids`);
-
- timeoutID = setTimeout(continueCallback, auctionDelay);
- processSubmoduleCallbacks(submodulesWithCallbacks, continueCallback);
- } else {
- // wait for auction complete before processing submodule callbacks
- events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() {
- events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler);
-
- // when syncDelay is zero, process callbacks now, otherwise delay process with a setTimeout
- if (syncDelay > 0) {
- setTimeout(function () {
- processSubmoduleCallbacks(submodulesWithCallbacks);
- }, syncDelay);
- } else {
- processSubmoduleCallbacks(submodulesWithCallbacks);
- }
- });
- }
+function idSystemInitializer({delay = delayFor} = {}) {
+ const startInit = promiseControls();
+ const startCallbacks = promiseControls();
+ let cancel;
+ let initialized = false;
+
+ function cancelAndTry(promise) {
+ if (cancel != null) {
+ cancel.reject(INIT_CANCELED);
+ }
+ cancel = promiseControls();
+ return Promise.race([promise, cancel.promise]);
+ }
+
+ // grab a reference to global vars so that the promise chains remain isolated;
+ // multiple calls to `init` (from tests) might otherwise cause them to interfere with each other
+ let initModules = initializedSubmodules;
+ let allModules = submodules;
+
+ function checkRefs(fn) {
+ // unfortunately tests have their own global state that needs to be guarded, so even if we keep ours tidy,
+ // we cannot let things like submodule callbacks run (they pollute things like the global `server` XHR mock)
+ return function(...args) {
+ if (initModules === initializedSubmodules && allModules === submodules) {
+ return fn(...args);
}
}
}
- if (continueAuction && !delayed) {
- continueAuction();
+ let done = cancelAndTry(
+ Promise.all([hooksReady, startInit.promise])
+ .then(() => gdprDataHandler.promise)
+ .then(checkRefs((consentData) => {
+ initSubmodules(initModules, allModules, consentData);
+ }))
+ .then(() => startCallbacks.promise)
+ .then(checkRefs(() => {
+ const modWithCb = initModules.filter(item => isFn(item.callback));
+ if (modWithCb.length) {
+ return new Promise((resolve) => processSubmoduleCallbacks(modWithCb, resolve));
+ }
+ }))
+ );
+
+ /**
+ * with `ready` = true, starts initialization; with `refresh` = true, reinitialize submodules (optionally
+ * filtered by `submoduleNames`).
+ */
+ return function ({refresh = false, submoduleNames = null, ready = false} = {}) {
+ if (ready && !initialized) {
+ initialized = true;
+ startInit.resolve();
+ // submodule callbacks should run immediately if `auctionDelay` > 0, or `syncDelay` ms after the
+ // auction ends otherwise
+ if (auctionDelay > 0) {
+ startCallbacks.resolve();
+ } else {
+ events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() {
+ events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler);
+ delay(syncDelay).then(startCallbacks.resolve);
+ });
+ }
+ }
+ if (refresh) {
+ done = cancelAndTry(
+ done
+ .catch(() => null)
+ .then(() => gdprDataHandler.promise) // fetch again in case a refresh was forced before this was resolved
+ .then(checkRefs((consentData) => {
+ const cbModules = initSubmodules(
+ initModules,
+ allModules.filter((sm) => submoduleNames == null || submoduleNames.includes(sm.submodule.name)),
+ consentData,
+ true
+ ).filter((sm) => {
+ return sm.callback != null;
+ });
+ if (cbModules.length) {
+ return new Promise((resolve) => processSubmoduleCallbacks(cbModules, resolve));
+ }
+ }))
+ );
+ }
+ return done;
+ };
+}
+
+let initIdSystem;
+
+function getPPID() {
+ // userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com
+ const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource);
+ if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') {
+ const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, '');
+ if (ppidValue.length >= 32 && ppidValue.length <= 150) {
+ return ppidValue;
+ } else {
+ logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`);
+ }
}
}
@@ -555,28 +640,23 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) {
* @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids.
* @param {function} fn required; The next function in the chain, used by hook.js
*/
-export function requestBidsHook(fn, reqBidsConfigObj) {
- // initialize submodules only when undefined
- initializeSubmodulesAndExecuteCallbacks(function () {
+export function requestBidsHook(fn, reqBidsConfigObj, {delay = delayFor} = {}) {
+ Promise.race([
+ getUserIdsAsync(),
+ delay(auctionDelay)
+ ]).then(() => {
// pass available user id data to bid adapters
addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules);
-
- // userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com
- const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource);
- if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') {
- const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, '');
- if (ppidValue.length >= 32 && ppidValue.length <= 150) {
- if (isGptPubadsDefined()) {
- window.googletag.pubads().setPublisherProvidedId(ppidValue);
- } else {
- window.googletag = window.googletag || {};
- window.googletag.cmd = window.googletag.cmd || [];
- window.googletag.cmd.push(function() {
- window.googletag.pubads().setPublisherProvidedId(ppidValue);
- });
- }
+ const ppid = getPPID();
+ if (ppid) {
+ if (isGptPubadsDefined()) {
+ window.googletag.pubads().setPublisherProvidedId(ppid);
} else {
- logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`);
+ window.googletag = window.googletag || {};
+ window.googletag.cmd = window.googletag.cmd || [];
+ window.googletag.cmd.push(function() {
+ window.googletag.pubads().setPublisherProvidedId(ppid);
+ });
}
}
@@ -590,9 +670,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) {
* Simple use case will be passing these UserIds to A9 wrapper solution
*/
function getUserIds() {
- // initialize submodules only when undefined
- initializeSubmodulesAndExecuteCallbacks();
- return getCombinedSubmoduleIds(initializedSubmodules);
+ return getCombinedSubmoduleIds(initializedSubmodules)
}
/**
@@ -600,63 +678,115 @@ function getUserIds() {
* Simple use case will be passing these UserIds to A9 wrapper solution
*/
function getUserIdsAsEids() {
- // initialize submodules only when undefined
- initializeSubmodulesAndExecuteCallbacks();
- return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules));
+ return createEidsArray(getUserIds())
}
/**
-* This function will be exposed in the global-name-space so that userIds can be refreshed after initialization.
-* @param {RefreshUserIdsOptions} options
-*/
-function refreshUserIds(options, callback) {
- let submoduleNames = options ? options.submoduleNames : null;
- if (!submoduleNames) {
- submoduleNames = [];
- }
-
- initializeSubmodulesAndExecuteCallbacks(function() {
- let consentData = gdprDataHandler.getConsentData()
-
- // gdpr consent with purpose one is required, otherwise exit immediately
- let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData);
- if (!hasValidated && !hasGDPRConsent(consentData)) {
- logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`);
- return;
- }
+ * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well.
+ * Simple use case will be passing these UserIds to A9 wrapper solution
+ */
- // we always want the latest consentData stored, even if we don't execute any submodules
- const storedConsentData = getStoredConsentData();
- setStoredConsentData(consentData);
+function getUserIdsAsEidBySource(sourceName) {
+ return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0];
+}
- let callbackSubmodules = [];
- for (let submodule of userIdModules) {
- if (submoduleNames.length > 0 &&
- submoduleNames.indexOf(submodule.submodule.name) === -1) {
- continue;
+/**
+ * This function will be exposed in global-name-space so that userIds for a source can be exposed
+ * Sample use case is exposing this function to ESP
+ */
+function getEncryptedEidsForSource(source, encrypt, customFunction) {
+ return initIdSystem().then(() => {
+ let eidsSignals = {};
+
+ if (isFn(customFunction)) {
+ logInfo(`${MODULE_NAME} - Getting encrypted signal from custom function : ${customFunction.name} & source : ${source} `);
+ // Publishers are expected to define a common function which will be proxy for signal function.
+ const customSignals = customFunction(source);
+ eidsSignals[source] = customSignals ? encryptSignals(customSignals) : null; // by default encrypt using base64 to avoid JSON errors
+ } else {
+ // initialize signal with eids by default
+ const eid = getUserIdsAsEidBySource(source);
+ logInfo(`${MODULE_NAME} - Getting encrypted signal for eids :${JSON.stringify(eid)}`);
+ if (!isEmpty(eid)) {
+ eidsSignals[eid.source] = encrypt === true ? encryptSignals(eid) : eid.uids[0].id; // If encryption is enabled append version (1||) and encrypt entire object
}
+ }
+ logInfo(`${MODULE_NAME} - Fetching encrypted eids: ${eidsSignals[source]}`);
+ return eidsSignals[source];
+ })
+}
- logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`);
- populateSubmoduleId(submodule, consentData, storedConsentData, true);
- updateInitializedSubmodules(submodule);
+function encryptSignals(signals, version = 1) {
+ let encryptedSig = '';
+ switch (version) {
+ case 1: // Base64 Encryption
+ encryptedSig = typeof signals === 'object' ? window.btoa(JSON.stringify(signals)) : window.btoa(signals); // Test encryption. To be replaced with better algo
+ break;
+ default:
+ break;
+ }
+ return `${version}||${encryptedSig}`;
+}
- if (initializedSubmodules.length) {
- setPrebidServerEidPermissions(initializedSubmodules);
- }
+/**
+* This function will be exposed in the global-name-space so that publisher can register the signals-ESP.
+*/
+function registerSignalSources() {
+ if (!isGptPubadsDefined()) {
+ return;
+ }
+ window.googletag.encryptedSignalProviders = window.googletag.encryptedSignalProviders || [];
+ const encryptedSignalSources = config.getConfig('userSync.encryptedSignalSources');
+ if (encryptedSignalSources) {
+ const registerDelay = encryptedSignalSources.registerDelay || 0;
+ setTimeout(() => {
+ encryptedSignalSources['sources'] && encryptedSignalSources['sources'].forEach(({ source, encrypt, customFunc }) => {
+ source.forEach((src) => {
+ window.googletag.encryptedSignalProviders.push({
+ id: src,
+ collectorFunction: () => getEncryptedEidsForSource(src, encrypt, customFunc)
+ });
+ });
+ })
+ }, registerDelay)
+ } else {
+ logWarn(`${MODULE_NAME} - ESP : encryptedSignalSources config not defined under userSync Object`);
+ }
+}
- if (isFn(submodule.callback)) {
- callbackSubmodules.push(submodule);
+/**
+ * Force (re)initialization of ID submodules.
+ *
+ * This will force a refresh of the specified ID submodules regardless of `auctionDelay` / `syncDelay` settings, and
+ * return a promise that resolves to the same value as `getUserIds()` when the refresh is complete.
+ * If a refresh is already in progress, it will be canceled (rejecting promises returned by previous calls to `refreshUserIds`).
+ *
+ * @param submoduleNames? submodules to refresh. If omitted, refresh all submodules.
+ * @param callback? called when the refresh is complete
+ */
+function refreshUserIds({submoduleNames} = {}, callback) {
+ return initIdSystem({refresh: true, submoduleNames})
+ .then(() => {
+ if (callback && isFn(callback)) {
+ callback();
}
- }
+ return getUserIds();
+ });
+}
- if (callbackSubmodules.length > 0) {
- processSubmoduleCallbacks(callbackSubmodules);
- }
+/**
+ * @returns a promise that resolves to the same value as `getUserIds()`, but only once all ID submodules have completed
+ * initialization. This can also be used to synchronize calls to other ID accessors, e.g.
+ *
+ * ```
+ * pbjs.getUserIdsAsync().then(() => {
+ * const eids = pbjs.getUserIdsAsEids(); // guaranteed to be completely initialized at this point
+ * });
+ * ```
+ */
- if (callback) {
- callback();
- }
- });
+function getUserIdsAsync() {
+ return initIdSystem().then(() => getUserIds(), (e) => e === INIT_CANCELED ? getUserIdsAsync() : Promise.reject(e));
}
/**
@@ -717,12 +847,7 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef
}
}
-/**
- * @param {SubmoduleContainer[]} submodules
- * @param {ConsentData} consentData
- * @returns {SubmoduleContainer[]} initialized submodules
- */
-function initSubmodules(submodules, consentData) {
+function initSubmodules(dest, submodules, consentData, forceRefresh = false) {
// gdpr consent with purpose one is required, otherwise exit immediately
let { userIdModules, hasValidated } = validateGdprEnforcement(submodules, consentData);
if (!hasValidated && !hasGDPRConsent(consentData)) {
@@ -734,25 +859,34 @@ function initSubmodules(submodules, consentData) {
const storedConsentData = getStoredConsentData();
setStoredConsentData(consentData);
- return userIdModules.reduce((carry, submodule) => {
- populateSubmoduleId(submodule, consentData, storedConsentData, false);
- carry.push(submodule);
+ const initialized = userIdModules.reduce((carry, submodule) => {
+ try {
+ populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh);
+ carry.push(submodule);
+ } catch (e) {
+ logError(`Error in userID module '${submodule.submodule.name}':`, e);
+ }
return carry;
}, []);
+ if (initialized.length) {
+ setPrebidServerEidPermissions(initialized);
+ }
+ initialized.forEach(updateInitializedSubmodules.bind(null, dest));
+ return initialized;
}
-function updateInitializedSubmodules(submodule) {
+function updateInitializedSubmodules(dest, submodule) {
let updated = false;
- for (let i = 0; i < initializedSubmodules.length; i++) {
- if (submodule.config.name.toLowerCase() === initializedSubmodules[i].config.name.toLowerCase()) {
+ for (let i = 0; i < dest.length; i++) {
+ if (submodule.config.name.toLowerCase() === dest[i].config.name.toLowerCase()) {
updated = true;
- initializedSubmodules[i] = submodule;
+ dest[i] = submodule;
break;
}
}
if (!updated) {
- initializedSubmodules.push(submodule);
+ dest.push(submodule);
}
}
@@ -801,8 +935,9 @@ function updateSubmodules() {
// do this to avoid reprocessing submodules
const addedSubmodules = submoduleRegistry.filter(i => !find(submodules, j => j.name === i.name));
+ submodules.splice(0, submodules.length);
// find submodule and the matching configuration, if found create and append a SubmoduleContainer
- submodules = addedSubmodules.map(i => {
+ addedSubmodules.map(i => {
const submoduleConfig = find(configs, j => j.name && (j.name.toLowerCase() === i.name.toLowerCase() ||
(i.aliasName && j.name.toLowerCase() === i.aliasName.toLowerCase())));
if (submoduleConfig && i.name !== submoduleConfig.name) submoduleConfig.name = i.name;
@@ -813,11 +948,13 @@ function updateSubmodules() {
callback: undefined,
idObj: undefined
} : null;
- }).filter(submodule => submodule !== null);
+ }).filter(submodule => submodule !== null)
+ .forEach((sm) => submodules.push(sm));
if (!addedUserIdHook && submodules.length) {
// priority value 40 will load after consentManagement with a priority of 50
getGlobal().requestBids.before(requestBidsHook, 40);
+ coreGetPPID.after((next) => next(getPPID()));
logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name));
addedUserIdHook = true;
}
@@ -839,12 +976,17 @@ export function attachIdSystem(submodule) {
* so a callback is added to fire after the consentManagement module.
* @param {{getConfig:function}} config
*/
-export function init(config) {
+export function init(config, {delay = delayFor} = {}) {
ppidSource = undefined;
submodules = [];
configRegistry = [];
addedUserIdHook = false;
- initializedSubmodules = undefined;
+ initializedSubmodules = [];
+ initIdSystem = idSystemInitializer({delay});
+ if (configListener != null) {
+ configListener();
+ }
+ submoduleRegistry = [];
// list of browser enabled storage types
validStorageTypes = [
@@ -863,7 +1005,7 @@ export function init(config) {
}
// listen for config userSyncs to be set
- config.getConfig('userSync', conf => {
+ configListener = config.getConfig('userSync', conf => {
// Note: support for 'usersync' was dropped as part of Prebid.js 4.0
const userSync = conf.userSync;
ppidSource = userSync.ppid;
@@ -872,13 +1014,18 @@ export function init(config) {
syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY;
auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY;
updateSubmodules();
+ initIdSystem({ready: true});
}
});
// exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes.
(getGlobal()).getUserIds = getUserIds;
(getGlobal()).getUserIdsAsEids = getUserIdsAsEids;
+ (getGlobal()).getEncryptedEidsForSource = getEncryptedEidsForSource;
+ (getGlobal()).registerSignalSources = registerSignalSources;
(getGlobal()).refreshUserIds = refreshUserIds;
+ (getGlobal()).getUserIdsAsync = getUserIdsAsync;
+ (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource;
}
// init config update listener to start the application
diff --git a/modules/userId/userId.md b/modules/userId/userId.md
index 44ed7003f0c..16f56ae7b9d 100644
--- a/modules/userId/userId.md
+++ b/modules/userId/userId.md
@@ -6,6 +6,17 @@ Example showing `cookie` storage for user id data for each of the submodules
pbjs.setConfig({
userSync: {
userIds: [{
+ name: "33acrossId",
+ storage: {
+ type: "cookie",
+ name: "33acrossId",
+ expires: 90,
+ refreshInSeconds: 8*3600
+ },
+ params: {
+ pid: "0010b00002GYU4eBAH" // Example ID
+ }
+ }, {
name: "pubCommonId",
storage: {
type: "cookie",
@@ -45,6 +56,17 @@ pbjs.setConfig({
expires: 90, // Expiration in days
refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires'
},
+ }, {
+ name: "ftrackId",
+ storage: {
+ type: "html5",
+ name: "ftrackId",
+ expires: 90,
+ refreshInSeconds: 8*3600
+ },
+ params: {
+ url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run
+ }
}, {
name: 'parrableId',
params: {
@@ -89,6 +111,8 @@ pbjs.setConfig({
name: '_criteoId',
expires: 1
}
+ }, {
+ name: "cpexId"
}, {
name: 'mwOpenLinkId',
params: {
@@ -140,6 +164,9 @@ pbjs.setConfig({
name: "knssoId",
expires: 30
},
+ {
+ name: "dacId"
+ }
],
syncDelay: 5000,
auctionDelay: 1000
@@ -152,17 +179,16 @@ Example showing `localStorage` for user id data for some submodules
```
pbjs.setConfig({
userSync: {
- userIds: [
- {
- name: 'trustpid',
- params: {
- maxDelayTime: 2500
- },
- bidders: ['adform'],
+ userIds: [{
+ name: "33acrossId",
storage: {
- type: 'html5',
- name: 'trustpid',
- expires: 60
+ type: "html5",
+ name: "33acrossId",
+ expires: 90,
+ refreshInSeconds: 8*3600
+ },
+ params: {
+ pid: "0010b00002GYU4eBAH" // Example ID
}
}, {
name: "unifiedId",
diff --git a/modules/userIdTargeting.js b/modules/userIdTargeting.js
index e15c9ddaca2..b7fd137779b 100644
--- a/modules/userIdTargeting.js
+++ b/modules/userIdTargeting.js
@@ -1,7 +1,7 @@
import {config} from '../src/config.js';
import {getGlobal} from '../src/prebidGlobal.js';
import CONSTANTS from '../src/constants.json';
-import events from '../src/events.js';
+import * as events from '../src/events.js';
import { isStr, isPlainObject, isBoolean, isFn, hasOwn, logInfo } from '../src/utils.js';
const MODULE_NAME = 'userIdTargeting';
diff --git a/modules/ventesBidAdapter.js b/modules/ventesBidAdapter.js
index a9de52a86ba..42292ddaed3 100644
--- a/modules/ventesBidAdapter.js
+++ b/modules/ventesBidAdapter.js
@@ -1,20 +1,7 @@
-import {
- BANNER,
- NATIVE,
- VIDEO
-} from '../src/mediaTypes.js';
-import {
- convertCamelToUnderscore,
- isStr,
- isArray,
- isNumber,
- isPlainObject,
- replaceAuctionPrice
-} from '../src/utils.js';
-import find from 'core-js-pure/features/array/find.js';
-import {
- registerBidder
-} from '../src/adapters/bidderFactory.js';
+import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
+import {convertCamelToUnderscore, isArray, isNumber, isPlainObject, isStr, replaceAuctionPrice} from '../src/utils.js';
+import {find} from '../src/polyfill.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
const BID_METHOD = 'POST';
const BIDDER_URL = 'http://13.234.201.146:8088/va/ad';
diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js
index 280a6c47894..27577ad0de4 100644
--- a/modules/verizonMediaIdSystem.js
+++ b/modules/verizonMediaIdSystem.js
@@ -7,8 +7,8 @@
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';
+import {formatQS, logError} from '../src/utils.js';
+import {includes} from '../src/polyfill.js';
const MODULE_NAME = 'verizonMediaId';
const VENDOR_ID = 25;
diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js
index b6fe51c43bc..0613f722af8 100644
--- a/modules/vibrantmediaBidAdapter.js
+++ b/modules/vibrantmediaBidAdapter.js
@@ -6,13 +6,14 @@
* Note: Only BANNER and VIDEO are currently supported by the prebid server.
*/
-import {logError, logInfo} from '../src/utils.js';
+import {logError, triggerPixel} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
import {OUTSTREAM} from '../src/video.js';
const BIDDER_CODE = 'vibrantmedia';
const VIBRANT_MEDIA_PREBID_URL = 'https://prebid.intellitxt.com/prebid';
+const VALID_PIXEL_URL_REGEX = /^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+([/?].*)?$/;
const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO];
/**
@@ -51,6 +52,10 @@ const isBaseUrl = function(url) {
return (endOfDomain === -1) || (endOfDomain === (urlMinusScheme.length - 1));
};
+const isValidPixelUrl = function (candidateUrl) {
+ return VALID_PIXEL_URL_REGEX.test(candidateUrl);
+};
+
/**
* Returns transformed bid requests that are in a format native to the prebid server.
*
@@ -213,7 +218,9 @@ export const spec = {
* @param {*} bidData the data associated with the won bid. See example above for data format.
*/
onBidWon: function(bidData) {
- logInfo('Bid won: ' + JSON.stringify(bidData));
+ if (bidData && bidData.meta && isValidPixelUrl(bidData.meta.wp)) {
+ triggerPixel(`${bidData.meta.wp}${bidData.status}`);
+ }
}
};
diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js
index b6ab42499b9..cf252bda2dc 100644
--- a/modules/vidazooBidAdapter.js
+++ b/modules/vidazooBidAdapter.js
@@ -24,7 +24,7 @@ export const SUPPORTED_ID_SYSTEMS = {
'pubcid': 1,
'tdid': 1,
};
-const storage = getStorageManager(GVLID);
+const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) {
return `https://${subDomain}.cootlogix.com`;
diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js
index d268f7a9d64..ffa4a290072 100644
--- a/modules/vidoomyBidAdapter.js
+++ b/modules/vidoomyBidAdapter.js
@@ -12,13 +12,9 @@ const GVLID = 380;
const COOKIE_SYNC_FALLBACK_URLS = [
'https://x.bidswitch.net/sync?ssp=vidoomy',
'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID',
- 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D',
- 'https://sync.1rx.io/usersync2/vidoomy?redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DUN%26uid%3D%5BRX_UUID%5D',
- 'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D',
- 'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D',
+ 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D',
'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID',
- 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}',
- 'https://ap.lijit.com/pixel?redir=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID'
+ 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}'
];
const isBidRequestValid = bid => {
diff --git a/modules/viewability.js b/modules/viewability.js
index b12c53b7f59..39b2ee3da16 100644
--- a/modules/viewability.js
+++ b/modules/viewability.js
@@ -1,6 +1,6 @@
-import { logWarn, logInfo, isStr, isFn, triggerPixel, insertHtmlIntoIframe } from '../src/utils.js';
-import { getGlobal } from '../src/prebidGlobal.js';
-import find from 'core-js-pure/features/array/find.js';
+import {insertHtmlIntoIframe, isFn, isStr, logInfo, logWarn, triggerPixel} from '../src/utils.js';
+import {getGlobal} from '../src/prebidGlobal.js';
+import {find} from '../src/polyfill.js';
export const MODULE_NAME = 'viewability';
diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js
index e3d02938c5b..9e0cb91af9b 100644
--- a/modules/viewdeosDXBidAdapter.js
+++ b/modules/viewdeosDXBidAdapter.js
@@ -1,8 +1,8 @@
-import { deepAccess, isArray, flatten, logError, parseSizesInput } from '../src/utils.js';
+import {deepAccess, flatten, isArray, logError, parseSizesInput} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {VIDEO, BANNER} from '../src/mediaTypes.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {Renderer} from '../src/Renderer.js';
-import findIndex from 'core-js-pure/features/array/find-index.js';
+import {findIndex} from '../src/polyfill.js';
const URL = 'https://ghb.sync.viewdeos.com/auction/';
const OUTSTREAM_SRC = 'https://player.sync.viewdeos.com/outstream-unit/2.01/outstream.min.js';
diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js
index af8672ea233..696d54e4b52 100644
--- a/modules/visxBidAdapter.js
+++ b/modules/visxBidAdapter.js
@@ -42,7 +42,7 @@ export const spec = {
}
}
}
- return !!bid.params.uid;
+ return !!bid.params.uid && !isNaN(parseInt(bid.params.uid));
},
buildRequests: function(validBidRequests, bidderRequest) {
const auids = [];
@@ -203,6 +203,15 @@ export const spec = {
},
onTimeout: function(timeoutData) {
// Call '/track/bid_timeout' with timeout data
+ timeoutData.forEach(({ params }) => {
+ if (params) {
+ params.forEach((item) => {
+ if (item && item.uid) {
+ item.uid = parseInt(item.uid);
+ }
+ });
+ }
+ });
triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(timeoutData));
}
};
@@ -249,7 +258,7 @@ function buildImpObject(bid) {
...(banner && { banner }),
...(video && { video }),
ext: {
- bidder: { uid: Number(uid) },
+ bidder: { uid: parseInt(uid) },
}
};
diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js
index 8db97800630..25dbbda90cf 100644
--- a/modules/voxBidAdapter.js
+++ b/modules/voxBidAdapter.js
@@ -1,7 +1,7 @@
-import { _map, logWarn, deepAccess, isArray } from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js'
-import {BANNER, VIDEO} from '../src/mediaTypes.js'
-import find from 'core-js-pure/features/array/find.js';
+import {_map, deepAccess, isArray, logWarn} from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {find} from '../src/polyfill.js';
import {auctionManager} from '../src/auctionManager.js';
import {Renderer} from '../src/Renderer.js';
diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js
index ee17a71dd35..1a97e3bd351 100644
--- a/modules/waardexBidAdapter.js
+++ b/modules/waardexBidAdapter.js
@@ -1,8 +1,8 @@
-import { logError, isArray, deepAccess, getBidIdParameter } from '../src/utils.js';
+import {deepAccess, getBidIdParameter, isArray, logError} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {config} from '../src/config.js';
-import find from 'core-js-pure/features/array/find.js';
+import {find} from '../src/polyfill.js';
const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid`;
const BIDDER_CODE = 'waardex';
diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js
index ae9c22928e8..64cdd6508bb 100644
--- a/modules/weboramaRtdProvider.js
+++ b/modules/weboramaRtdProvider.js
@@ -7,57 +7,96 @@
* @requires module:modules/realTimeData
*/
+/**
+ * @typedef dataCallbackMetadata
+ * @property {Boolean} user if true it is user-centric data
+ * @property {String} source describe the source of data, if "contextual" or "wam"
+ * @property {Boolean} isDefault if true it the default profile defined in the configuration
+ */
+
/** onData callback type
* @callback dataCallback
* @param {Object} data profile data
- * @param {Boolean} site true if site, else it is user
+ * @param {dataCallbackMetadata} meta metadata
* @returns {void}
*/
+/** setPrebidTargeting callback type
+ * @callback setPrebidTargetingCallback
+ * @param {String} adUnitCode
+ * @param {Object} data
+ * @param {dataCallbackMetadata} metadata
+ * @returns {Boolean}
+ */
+
+/** sendToBidders callback type
+ * @callback sendToBiddersCallback
+ * @param {Object} bid
+ * @param {String} adUnitCode
+ * @param {Object} data
+ * @param {dataCallbackMetadata} metadata
+ * @returns {Boolean}
+ */
+
/**
* @typedef {Object} ModuleParams
- * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default undefined)
- * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default undefined)
+ * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined)
+ * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined)
* @property {?dataCallback} onData callback
- * @property {?WeboCtxConf} weboCtxConf
- * @property {?WeboUserDataConf} weboUserDataConf
+ * @property {?WeboCtxConf} weboCtxConf site-centric contextual configuration
+ * @property {?WeboUserDataConf} weboUserDataConf user-centric wam configuration
+ * @property {?SfbxLiteDataConf} sfbxLiteDataConf site-centric lite configuration
*/
/**
* @typedef {Object} WeboCtxConf
* @property {string} token required token to be used on bigsea contextual API requests
* @property {?string} targetURL specify the target url instead use the referer
- * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true)
- * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default params.sendToBidders or true)
+ * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined)
+ * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined)
* @property {?dataCallback} onData callback
* @property {?object} defaultProfile to be used if the profile is not found
* @property {?Boolean} enabled if false, will ignore this configuration
+ * @property {?string} baseURLProfileAPI to be used to point to a different domain than ctx.weborama.com
*/
/**
* @typedef {Object} WeboUserDataConf
* @property {?number} accountId wam account id
- * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true)
- * @property {?Boolean} sendToBidders if true, will send the user-centric profile to all bidders (default params.sendToBidders or true)
+ * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined)
+ * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined)
* @property {?object} defaultProfile to be used if the profile is not found
* @property {?dataCallback} onData callback
* @property {?string} localStorageProfileKey can be used to customize the local storage key (default is 'webo_wam2gam_entry')
* @property {?Boolean} enabled if false, will ignore this configuration
*/
+/**
+ * @typedef {Object} SfbxLiteDataConf
+ * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined)
+ * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined)
+ * @property {?object} defaultProfile to be used if the profile is not found
+ * @property {?dataCallback} onData callback
+ * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is '_lite')
+ * @property {?Boolean} enabled if false, will ignore this configuration
+ */
import {
getGlobal
} from '../src/prebidGlobal.js';
import {
deepSetValue,
- deepAccess,
isEmpty,
mergeDeep,
logError,
logWarn,
tryAppendQueryString,
logMessage,
- isFn
+ isFn,
+ isArray,
+ isStr,
+ isBoolean,
+ isPlainObject,
+ deepClone,
} from '../src/utils.js';
import {
submodule
@@ -68,21 +107,36 @@ import {
import {
getStorageManager
} from '../src/storageManager.js';
-
-const adapterManager = require('../src/adapterManager.js').default;
+import adapterManager from '../src/adapterManager.js';
/** @type {string} */
const MODULE_NAME = 'realTimeData';
/** @type {string} */
const SUBMODULE_NAME = 'weborama';
/** @type {string} */
+const BASE_URL_CONTEXTUAL_PROFILE_API = 'ctx.weborama.com';
+/** @type {string} */
export const DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY = 'webo_wam2gam_entry';
/** @type {string} */
const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting';
+/** @type {string} */
+export const DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY = '_lite';
+/** @type {string} */
+const LOCAL_STORAGE_LITE_TARGETING_SECTION = 'webo';
+/** @type {string} */
+const WEBO_CTX_CONF_SECTION = 'weboCtxConf';
+/** @type {string} */
+const WEBO_USER_DATA_CONF_SECTION = 'weboUserDataConf';
+/** @type {string} */
+const SFBX_LITE_DATA_CONF_SECTION = 'sfbxLiteDataConf';
+
/** @type {number} */
const GVLID = 284;
-/** @type {object} */
-export const storage = getStorageManager(GVLID, SUBMODULE_NAME);
+/** @type {?Object} */
+export const storage = getStorageManager({
+ gvlid: GVLID,
+ moduleName: SUBMODULE_NAME
+});
/** @type {null|Object} */
let _weboContextualProfile = null;
@@ -90,78 +144,67 @@ let _weboContextualProfile = null;
/** @type {Boolean} */
let _weboCtxInitialized = false;
-/** @type {null|Object} */
+/** @type {?Object} */
let _weboUserDataUserProfile = null;
/** @type {Boolean} */
let _weboUserDataInitialized = false;
+/** @type {?Object} */
+let _sfbxLiteDataProfile = null;
+
+/** @type {Boolean} */
+let _sfbxLiteDataInitialized = false;
+
/** Initialize module
* @param {object} moduleConfig
* @return {Boolean} true if module was initialized with success
*/
function init(moduleConfig) {
- moduleConfig = moduleConfig || {};
- const moduleParams = moduleConfig.params || {};
- const weboCtxConf = moduleParams.weboCtxConf;
- const weboUserDataConf = moduleParams.weboUserDataConf;
-
- _weboCtxInitialized = initWeboCtx(moduleParams, weboCtxConf);
- _weboUserDataInitialized = initWeboUserData(moduleParams, weboUserDataConf);
-
- return _weboCtxInitialized || _weboUserDataInitialized;
-}
-
-/** Initialize contextual sub module
- * @param {ModuleParams} moduleParams
- * @param {WeboCtxConf} weboCtxConf
- * @return {Boolean} true if sub module was initialized with success
- */
-function initWeboCtx(moduleParams, weboCtxConf) {
- if (!weboCtxConf || weboCtxConf.enabled === false) {
- moduleParams.weboCtxConf = null;
+ const moduleParams = moduleConfig?.params || {};
- return false
- }
-
- normalizeConf(moduleParams, weboCtxConf);
-
- _weboCtxInitialized = false;
_weboContextualProfile = null;
+ _weboUserDataUserProfile = null;
+ _sfbxLiteDataProfile = null;
- if (!weboCtxConf.token) {
- logWarn('missing param "token" for weborama contextual sub module initialization');
- return false;
- }
-
- logMessage('weborama contextual intialized with success');
+ _weboCtxInitialized = initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token');
+ _weboUserDataInitialized = initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION);
+ _sfbxLiteDataInitialized = initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION);
- return true;
+ return _weboCtxInitialized || _weboUserDataInitialized || _sfbxLiteDataInitialized;
}
-/** Initialize weboUserData sub module
- * @param {ModuleParams} moduleParams
- * @param {WeboUserDataConf} weboUserDataConf
- * @return {Boolean} true if sub module was initialized with success
+/** Initialize subsection module
+ * @param {Object} moduleParams
+ * @param {string} subSection subsection name to initialize
+ * @param {[]string} requiredFields
+ * @return {Boolean} true if module subsection was initialized with success
*/
-function initWeboUserData(moduleParams, weboUserDataConf) {
- if (!weboUserDataConf || weboUserDataConf.enabled === false) {
- moduleParams.weboUserDataConf = null;
+function initSubSection(moduleParams, subSection, ...requiredFields) {
+ const weboSectionConf = moduleParams[subSection] || {enabled: false};
+
+ if (weboSectionConf.enabled === false) {
+ delete moduleParams[subSection];
return false;
}
- normalizeConf(moduleParams, weboUserDataConf);
+ requiredFields ||= [];
- _weboUserDataInitialized = false;
- _weboUserDataUserProfile = null;
+ try {
+ normalizeConf(moduleParams, weboSectionConf);
- let message = 'weborama user-centric intialized with success';
- if (weboUserDataConf.hasOwnProperty('accountId')) {
- message = `weborama user-centric intialized with success for account: ${weboUserDataConf.accountId}`;
+ requiredFields.forEach(field => {
+ if (!weboSectionConf[field]) {
+ throw `missing required field "{field}" on {section}`;
+ }
+ });
+ } catch (e) {
+ logError(`unable to initialize: error on ${subSection} configuration: ${e}`);
+ return false
}
- logMessage(message);
+ logMessage(`weborama ${subSection} initialized with success`);
return true;
}
@@ -170,21 +213,172 @@ function initWeboUserData(moduleParams, weboUserDataConf) {
const globalDefaults = {
setPrebidTargeting: true,
sendToBidders: true,
- onData: (data, kind, def) => logMessage('onData(data,kind,default)', data, kind, def),
+ onData: () => {
+ /* do nothing */ },
}
/** normalize submodule configuration
* @param {ModuleParams} moduleParams
- * @param {WeboCtxConf|WeboUserDataConf} submoduleParams
+ * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams
* @return {void}
*/
function normalizeConf(moduleParams, submoduleParams) {
+ // handle defaults
Object.entries(globalDefaults).forEach(([propertyName, globalDefaultValue]) => {
if (!submoduleParams.hasOwnProperty(propertyName)) {
const hasModuleParam = moduleParams.hasOwnProperty(propertyName);
submoduleParams[propertyName] = (hasModuleParam) ? moduleParams[propertyName] : globalDefaultValue;
}
})
+
+ // handle setPrebidTargeting
+ coerceSetPrebidTargeting(submoduleParams)
+
+ // handle sendToBidders
+ coerceSendToBidders(submoduleParams)
+
+ if (!isFn(submoduleParams.onData)) {
+ throw 'onData parameter should be a callback';
+ }
+
+ submoduleParams.defaultProfile = submoduleParams.defaultProfile || {};
+
+ if (!isValidProfile(submoduleParams.defaultProfile)) {
+ throw 'defaultProfile is not valid';
+ }
+}
+
+/** coerce set prebid targeting to function
+ * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams
+ * @return {void}
+ */
+function coerceSetPrebidTargeting(submoduleParams) {
+ const setPrebidTargeting = submoduleParams.setPrebidTargeting;
+
+ if (isFn(setPrebidTargeting)) {
+ return
+ }
+
+ if (isBoolean(setPrebidTargeting)) {
+ const shouldSetPrebidTargeting = setPrebidTargeting;
+
+ submoduleParams.setPrebidTargeting = () => shouldSetPrebidTargeting;
+
+ return
+ }
+
+ if (isStr(setPrebidTargeting)) {
+ const allowedAdUnitCode = setPrebidTargeting;
+
+ submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCode == adUnitCode;
+
+ return
+ }
+
+ if (isArray(setPrebidTargeting)) {
+ const allowedAdUnitCodes = setPrebidTargeting;
+
+ submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCodes.includes(adUnitCode);
+
+ return
+ }
+
+ throw `unexpected format for setPrebidTargeting: ${typeof setPrebidTargeting}`;
+}
+
+/** coerce send to bidders to function
+ * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams
+ * @return {void}
+ */
+function coerceSendToBidders(submoduleParams) {
+ const sendToBidders = submoduleParams.sendToBidders;
+
+ if (isFn(sendToBidders)) {
+ return
+ }
+
+ if (isBoolean(sendToBidders)) {
+ const shouldSendToBidders = sendToBidders;
+
+ submoduleParams.sendToBidders = () => shouldSendToBidders;
+
+ return
+ }
+
+ if (isStr(sendToBidders)) {
+ const allowedBidder = sendToBidders;
+
+ submoduleParams.sendToBidders = (bid) => allowedBidder == bid.bidder;
+
+ return
+ }
+
+ if (isArray(sendToBidders)) {
+ const allowedBidders = sendToBidders;
+
+ submoduleParams.sendToBidders = (bid) => allowedBidders.includes(bid.bidder);
+
+ return
+ }
+
+ if (isPlainObject(sendToBidders)) {
+ const sendToBiddersMap = sendToBidders;
+ submoduleParams.sendToBidders = (bid, adUnitCode) => {
+ const bidder = bid.bidder;
+ if (!sendToBiddersMap.hasOwnProperty(bidder)) {
+ return false
+ }
+
+ const value = sendToBiddersMap[bidder];
+
+ if (isBoolean(value)) {
+ return value
+ }
+
+ if (isStr(value)) {
+ return value == adUnitCode
+ }
+
+ if (isArray(value)) {
+ return value.includes(adUnitCode)
+ }
+
+ throw `unexpected format for sendToBidders[${bidder}]: ${typeof value}`;
+ };
+
+ return
+ }
+
+ throw `unexpected format for sendToBidders: ${typeof sendToBidders}`;
+}
+/**
+ * check if profile is valid
+ * @param {*} profile
+ * @returns {Boolean}
+ */
+function isValidProfile(profile) {
+ if (!isPlainObject(profile)) {
+ return false;
+ }
+
+ const keys = Object.keys(profile);
+
+ for (var i in keys) {
+ const key = keys[i];
+ const value = profile[key];
+ if (!isArray(value)) {
+ return false;
+ }
+
+ for (var j in value) {
+ const elem = value[j]
+ if (!isStr(elem)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
}
/** function that provides ad server targeting data to RTD-core
@@ -193,24 +387,27 @@ function normalizeConf(moduleParams, submoduleParams) {
* @returns {Object} target data
*/
function getTargetingData(adUnitsCodes, moduleConfig) {
- moduleConfig = moduleConfig || {};
- const moduleParams = moduleConfig.params || {};
- const weboCtxConf = moduleParams.weboCtxConf || {};
- const weboUserDataConf = moduleParams.weboUserDataConf || {};
- const weboCtxConfTargeting = weboCtxConf.setPrebidTargeting;
- const weboUserDataConfTargeting = weboUserDataConf.setPrebidTargeting;
+ const moduleParams = moduleConfig?.params || {};
- try {
- const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting);
+ const profileHandlers = buildProfileHandlers(moduleParams);
- if (isEmpty(profile)) {
- return {};
- }
+ if (isEmpty(profileHandlers)) {
+ logMessage('no data to set targeting');
+ return {};
+ }
+ try {
const td = adUnitsCodes.reduce((data, adUnitCode) => {
- if (adUnitCode) {
- data[adUnitCode] = profile;
- }
+ data[adUnitCode] = profileHandlers.reduce((targeting, ph) => {
+ // logMessage(`check if should set targeting for adunit '${adUnitCode}'`);
+ const cph = copyProfileHandler(ph);
+ if (ph.setTargeting(adUnitCode, cph.data, cph.metadata)) {
+ // logMessage(`set targeting for adunit '${adUnitCode}', source '${ph.metadata.source}'`);
+
+ mergeDeep(targeting, cph.data);
+ }
+ return targeting;
+ }, {});
return data;
}, {});
@@ -221,57 +418,155 @@ function getTargetingData(adUnitsCodes, moduleConfig) {
}
}
-/** function that provides complete profile formatted to be used
+/** function that provides data handlers based on the configuration
* @param {ModuleParams} moduleParams
- * @param {Boolean} weboCtxConfTargeting
- * @param {Boolean} weboUserDataConfTargeting
- * @returns {Object} complete profile
+ * @returns {Array